From ef0df70bc66edec205107ba175c910fe7bc45fa1 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 25 May 2023 18:18:34 +0200 Subject: [PATCH 001/159] [driver] Add MCP3008 SPI ADC driver --- README.md | 12 +++-- src/modm/driver/adc/mcp3008.hpp | 80 ++++++++++++++++++++++++++++ src/modm/driver/adc/mcp3008.lb | 30 +++++++++++ src/modm/driver/adc/mcp3008_impl.hpp | 50 +++++++++++++++++ 4 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/modm/driver/adc/mcp3008.hpp create mode 100644 src/modm/driver/adc/mcp3008.lb create mode 100644 src/modm/driver/adc/mcp3008_impl.hpp diff --git a/README.md b/README.md index 247e0d8819..eed411dfd4 100644 --- a/README.md +++ b/README.md @@ -770,45 +770,47 @@ you specific needs. MCP23x17 MCP2515 +MCP3008 MCP7941x MCP990X MMC5603 MS5611 -MS5837 +MS5837 NOKIA5110 NRF24 TFT-DISPLAY PAT9125EL PCA8574 -PCA9535 +PCA9535 PCA9548A PCA9685 SH1106 SIEMENS-S65 SIEMENS-S75 -SK6812 +SK6812 SK9822 SSD1306 ST7586S ST7789 STTS22H -STUSB4500 +STUSB4500 SX1276 TCS3414 TCS3472 TLC594x TMP102 -TMP12x +TMP12x TMP175 TOUCH2046 VL53L0 VL6180 WS2812 + diff --git a/src/modm/driver/adc/mcp3008.hpp b/src/modm/driver/adc/mcp3008.hpp new file mode 100644 index 0000000000..644328ec95 --- /dev/null +++ b/src/modm/driver/adc/mcp3008.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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_MCP3008_HPP +#define MODM_MCP3008_HPP + +#include +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_mcp3008 +struct mcp3008 +{ + /** + * ADC channels + * For MCP3004 only channels 0-3 are valid. + */ + enum class + Channel : uint8_t + { + Ch0 = 0b1000, + Ch1 = 0b1001, + Ch2 = 0b1010, + Ch3 = 0b1011, + Ch4 = 0b1100, + Ch5 = 0b1101, + Ch6 = 0b1110, + Ch7 = 0b1111, + Ch0Ch1Diff = 0b0000, + Ch1Ch0Diff = 0b0001, + Ch2Ch3Diff = 0b0010, + Ch3Ch2Diff = 0b0011, + Ch4Ch5Diff = 0b0100, + Ch5Ch4Diff = 0b0101, + Ch6Ch7Diff = 0b0110, + Ch7Ch6Diff = 0b0111 + }; +}; + +/** + * Simple driver for MCP3004/MCP3008 10-bit SAR SPI ADCs. + * Max SPI frequency 3.6 MHz at 5V, 1.36 MHz at 2.7V. + * + * @author Christopher Durand + * @ingroup modm_driver_mcp3008 + */ +template +class Mcp3008 : public mcp3008, public modm::SpiDevice, protected modm::NestedResumable<1> +{ +public: + Mcp3008() = default; + + /// Call initialize() before reading ADC + void initialize(); + + /// Read ADC channel + modm::ResumableResult + read(Channel channel); + +private: + uint8_t rxBuffer_[3]{}; + uint8_t txBuffer_[3]{1, 0, 0}; +}; + +} // namespace modm + +#include "mcp3008_impl.hpp" + +#endif // MODM_MCP3008_HPP diff --git a/src/modm/driver/adc/mcp3008.lb b/src/modm/driver/adc/mcp3008.lb new file mode 100644 index 0000000000..ddf5916c41 --- /dev/null +++ b/src/modm/driver/adc/mcp3008.lb @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Christopher Durand +# +# 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 = ":driver:mcp3008" + module.description = """\ +# MCP3004/MCP3008 ADC +MCP3004/3008 are 10-bit 200 ksps SAR ADCs with SPI interface and 4/8 channels. +""" + +def prepare(module, options): + module.depends( + ":architecture:spi.device", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/adc" + env.copy("mcp3008.hpp") + env.copy("mcp3008_impl.hpp") diff --git a/src/modm/driver/adc/mcp3008_impl.hpp b/src/modm/driver/adc/mcp3008_impl.hpp new file mode 100644 index 0000000000..881dad70cf --- /dev/null +++ b/src/modm/driver/adc/mcp3008_impl.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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_MCP3008_HPP +#error "Don't include this file directly, use 'mcp3008.hpp' instead!" +#endif + +namespace modm +{ + +template +void +Mcp3008::initialize() +{ + this->attachConfigurationHandler([]() { + SpiMaster::setDataMode(SpiMaster::DataMode::Mode0); + }); + + Cs::setOutput(true); +} + +template +modm::ResumableResult +Mcp3008::read(Channel channel) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + txBuffer_[1] = static_cast(channel) << 4; + + RF_CALL(SpiMaster::transfer(txBuffer_, rxBuffer_, 3)); + + if (this->releaseMaster()) { + Cs::set(); + } + + RF_END_RETURN(uint16_t(((rxBuffer_[1] & 0b11) << 8) | rxBuffer_[2])); +} + +} // namespace modm From eda224ec0c7a3f72cc0be25fa89a346a3eccd31c Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 25 May 2023 18:54:45 +0200 Subject: [PATCH 002/159] [example] Add MCP3008 SPI ADC example for SAMV71 Xplained Ultra --- .../samv71_xplained_ultra/mcp3008/main.cpp | 58 +++++++++++++++++++ .../samv71_xplained_ultra/mcp3008/project.xml | 11 ++++ 2 files changed, 69 insertions(+) create mode 100644 examples/samv71_xplained_ultra/mcp3008/main.cpp create mode 100644 examples/samv71_xplained_ultra/mcp3008/project.xml diff --git a/examples/samv71_xplained_ultra/mcp3008/main.cpp b/examples/samv71_xplained_ultra/mcp3008/main.cpp new file mode 100644 index 0000000000..b2ad1d2c16 --- /dev/null +++ b/examples/samv71_xplained_ultra/mcp3008/main.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 + +using namespace Board; +using namespace modm::platform; + +using SpiMaster = SpiMaster0; +using Sck = GpioD22; +using Mosi = GpioD21; +using Miso = GpioD20; +using Cs = GpioD25; + +int main() +{ + Board::initialize(); + + MODM_LOG_INFO << "MCP3004/8 ADC example" << modm::endl; + + SpiMaster::connect(); + Cs::setOutput(true); + + SpiMaster::initialize(); + + modm::Mcp3008 adc; + adc.initialize(); + + constexpr std::array channels = { + std::make_pair(0, modm::mcp3008::Channel::Ch0), + std::make_pair(1, modm::mcp3008::Channel::Ch1), + std::make_pair(2, modm::mcp3008::Channel::Ch2), + std::make_pair(3, modm::mcp3008::Channel::Ch3) + }; + + while (true) { + for (auto [i, ch] : channels) { + const auto value = RF_CALL_BLOCKING(adc.read(ch)); + MODM_LOG_INFO << "channel " << i << ": " << value << '\n'; + } + + Led0::toggle(); + Led1::toggle(); + modm::delay(1s); + } + + return 0; +} diff --git a/examples/samv71_xplained_ultra/mcp3008/project.xml b/examples/samv71_xplained_ultra/mcp3008/project.xml new file mode 100644 index 0000000000..6d11a839de --- /dev/null +++ b/examples/samv71_xplained_ultra/mcp3008/project.xml @@ -0,0 +1,11 @@ + + modm:samv71-xplained-ultra + + + + + modm:build:scons + modm:driver:mcp3008 + modm:platform:spi:0 + + From 1f0251c2cb299cdc9a8128f82b0a138a4e52a81a Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Sun, 4 Jun 2023 17:27:39 +0200 Subject: [PATCH 003/159] [driver] Fix MS5611 calibrated value calculation --- src/modm/driver/pressure/ms5611_data_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/driver/pressure/ms5611_data_impl.hpp b/src/modm/driver/pressure/ms5611_data_impl.hpp index d1c515a4e5..265a13ae6c 100644 --- a/src/modm/driver/pressure/ms5611_data_impl.hpp +++ b/src/modm/driver/pressure/ms5611_data_impl.hpp @@ -51,7 +51,7 @@ Data::calculateCalibratedValues() // Compute intermediate values used to calculate the pressure and temperature int32_t dT = rawTemp - (uint32_t(prom.data[5]) << 8); int64_t off = (int64_t(prom.data[2]) << 16) + ((int64_t(prom.data[4]) * int64_t(dT)) >> 7); - int64_t sens = (int64_t(prom.data[1]) << 15) + ((int64_t(prom.data[5]) * int64_t(dT)) >> 8); + int64_t sens = (int64_t(prom.data[1]) << 15) + ((int64_t(prom.data[3]) * int64_t(dT)) >> 8); // Compute temperature without second order compensation int32_t temp = 2000 + ((int64_t(dT) * prom.data[6]) >> 23); From 4f766a66e36dc12a47e0828c3c45800eb5278c46 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Jun 2023 00:52:54 +0200 Subject: [PATCH 004/159] [stm32] Enable STM32G0B/C devices --- README.md | 6 +++--- test/all/ignored.txt | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index eed411dfd4..dd91b83eb5 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ git clone --recurse-submodules --jobs 8 https://github.com/modm-io/modm.git ## Microcontrollers -modm can create a HAL for 3628 devices of these vendors: +modm can create a HAL for 3715 devices of these vendors: -- STMicroelectronics STM32: 2823 devices. +- STMicroelectronics STM32: 2910 devices. - Microchip SAM: 416 devices. - Microchip AVR: 388 devices. - Raspberry Pi: 1 device. @@ -164,7 +164,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -✕ +✅ ✅ ○ ✕ diff --git a/test/all/ignored.txt b/test/all/ignored.txt index d6ed40b370..f5ee44df96 100644 --- a/test/all/ignored.txt +++ b/test/all/ignored.txt @@ -106,10 +106,6 @@ samd21e15c-uf samd21e15c-uu samd21e16c-uf samd21e16c-uu -# FIXME: Shared IRQs on UART, SPI, I2C -stm32g0b0 -stm32g0b1 -stm32g0c1 # FIXME: Dual-core devices not supported yet stm32h745 stm32h755 From 8f29894422c4239338e7b05ee3bd446ed90ff8ff Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Jun 2023 00:53:27 +0200 Subject: [PATCH 005/159] [stm32] Fix platform startup for STM32G0B/C --- src/modm/platform/core/stm32/startup_platform.c.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/core/stm32/startup_platform.c.in b/src/modm/platform/core/stm32/startup_platform.c.in index d6238bd927..5764524b36 100644 --- a/src/modm/platform/core/stm32/startup_platform.c.in +++ b/src/modm/platform/core/stm32/startup_platform.c.in @@ -63,7 +63,7 @@ __modm_initialize_platform(void) %% elif target.family in ["g0", "g4", "l4", "l5"] %% if target.family in ["l4", "g4"] RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN; -%% else +%% elif target.family != "g0" #ifdef PWR_CR2_IOSV RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN; #endif From e7ea251ed15d192b07ad9997361296d62ac83fbd Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Jun 2023 00:58:27 +0200 Subject: [PATCH 006/159] [stm32] Add STM32G0B/C support to I2C driver --- .../i2c/stm32-extended/i2c_master.cpp.in | 32 +++++++++++++---- .../i2c/stm32-extended/i2c_master.hpp.in | 9 +++++ .../i2c/stm32-extended/i2c_shared_irqs.cpp.in | 25 +++++++++++++ .../platform/i2c/stm32-extended/module.lb | 35 +++++++++++++++++-- 4 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 src/modm/platform/i2c/stm32-extended/i2c_shared_irqs.cpp.in diff --git a/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in index c620cca8db..c8c1e50bb0 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in @@ -2,6 +2,7 @@ /* * Copyright (c) 2017, Niklas Hauser * Copyright (c) 2017, Sascha Schade + * Copyright (c) 2018, 2023, Christopher Durand * * This file is part of the modm project. * @@ -53,7 +54,7 @@ #include #include -%% if not shared_interrupt +%% if not single_interrupt MODM_ISR_DECL(I2C{{ id }}_ER); %% endif @@ -323,7 +324,10 @@ namespace } // ---------------------------------------------------------------------------- -%% if shared_interrupt +%% if shared_irq +void +modm::platform::I2cMaster{{ id }}::irq() +%% elif single_interrupt MODM_ISR(I2C{{ id }}) %% else MODM_ISR(I2C{{ id }}_EV) @@ -331,12 +335,24 @@ MODM_ISR(I2C{{ id }}_EV) { DEBUG_STREAM("\n=== IRQ ==="); -%% if shared_interrupt +%% if single_interrupt handleError(); %% endif - uint16_t isr = I2C{{ id }}->ISR; + const uint16_t isr = I2C{{ id }}->ISR; +%% if shared_irq + const auto cr1 = I2C{{ id }}->CR1; + const auto mask = (cr1 & (I2C_CR1_STOPIE | I2C_CR1_RXIE | I2C_CR1_TXIE)) + | ((cr1 & I2C_CR1_TCIE) ? (I2C_ISR_TCR | I2C_ISR_TC) : 0u); + + static_assert(I2C_CR1_STOPIE == I2C_ISR_STOPF); + static_assert(I2C_CR1_TXIE == I2C_ISR_TXIS); + static_assert(I2C_CR1_RXIE == I2C_ISR_RXNE); + if ((isr & mask) == 0) { + return; + } +%% endif I2C{{ id }}->CR1 &= ~(I2C_CR1_STOPIE | I2C_CR1_TCIE | I2C_CR1_RXIE | I2C_CR1_TXIE); I2C{{ id }}->ICR = I2C_ICR_STOPCF; @@ -487,7 +503,7 @@ MODM_ISR(I2C{{ id }}_EV) } // ---------------------------------------------------------------------------- -%% if not shared_interrupt +%% if not single_interrupt MODM_ISR(I2C{{ id }}_ER) { handleError(); @@ -539,7 +555,11 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint32_t timingRegist // Disable Own Address 2 I2C{{ id }}->OAR2 = 0; -%% if shared_interrupt +%% if shared_irq + // Enable Interrupt + NVIC_SetPriority({{ shared_irq }}_IRQn, 10); + NVIC_EnableIRQ({{ shared_irq }}_IRQn); +%% elif single_interrupt // Enable Interrupt NVIC_SetPriority(I2C{{ id }}_IRQn, 10); NVIC_EnableIRQ(I2C{{ id }}_IRQn); diff --git a/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in index d6e61f36b0..1b4b439747 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in @@ -2,6 +2,7 @@ /* * Copyright (c) 2017, Sascha Schade * Copyright (c) 2017-2018, Niklas Hauser + * Copyright (c) 2018, 2023, Christopher Durand * * This file is part of the modm project. * @@ -14,6 +15,7 @@ #ifndef MODM_STM32_I2C_{{ id }}_HPP #define MODM_STM32_I2C_{{ id }}_HPP +#include #include #include #include @@ -117,6 +119,13 @@ public: private: static void initializeWithPrescaler(uint32_t timingRegisterValue); + +%% if shared_irq + friend void ::{{ shared_irq }}_IRQHandler(); + + static void + irq(); +%% endif }; diff --git a/src/modm/platform/i2c/stm32-extended/i2c_shared_irqs.cpp.in b/src/modm/platform/i2c/stm32-extended/i2c_shared_irqs.cpp.in new file mode 100644 index 0000000000..c731ee219d --- /dev/null +++ b/src/modm/platform/i2c/stm32-extended/i2c_shared_irqs.cpp.in @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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/. + */ +// ---------------------------------------------------------------------------- + +%% for ids in shared_irqs.values() +%% for id in ids | sort +#include "i2c_master_{{ id }}.hpp" +%% endfor +%% endfor + +%% for irq,ids in shared_irqs.items() +MODM_ISR({{ irq }}) +{ +%% for id in ids | sort + modm::platform::I2cMaster{{ id }}::irq(); +%% endfor +} +%% endfor diff --git a/src/modm/platform/i2c/stm32-extended/module.lb b/src/modm/platform/i2c/stm32-extended/module.lb index 6e6b643673..970bc19be7 100644 --- a/src/modm/platform/i2c/stm32-extended/module.lb +++ b/src/modm/platform/i2c/stm32-extended/module.lb @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2016-2018, Niklas Hauser # Copyright (c) 2017, Fabian Greif +# Copyright (c) 2023, Christopher Durand # # This file is part of the modm project. # @@ -10,6 +11,23 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # ----------------------------------------------------------------------------- +from collections import defaultdict +import re + +global_properties = {} + +def get_shared_irqs(device): + irq_re = re.compile("I2C\d(_\d)+") + shared_irqs = [v["name"] for v in device.get_driver("core")["vector"]] + shared_irqs = [v for v in shared_irqs if irq_re.fullmatch(v)] + shared_irq_map = {} + for irq in shared_irqs: + instances = tuple(irq[3:].split("_")) + for i in instances: + shared_irq_map[int(i)] = irq + return shared_irq_map + + class Instance(Module): def __init__(self, instance): self.instance = instance @@ -31,13 +49,21 @@ class Instance(Module): return True def build(self, env): + global global_properties + device = env[":target"] driver = device.get_driver("i2c") properties = device.properties properties["target"] = target = device.identifier properties["id"] = self.instance - properties["shared_interrupt"] = "0" in target.family + properties["single_interrupt"] = "0" in target.family + shared_irq = get_shared_irqs(device).get(self.instance) + properties["shared_irq"] = shared_irq + if shared_irq: + global_properties["shared_irqs"][shared_irq].append(self.instance) + # shared irq is only supported with a single data and error interrupt + assert properties["single_interrupt"] or not shared_irq env.substitutions = properties env.outbasepath = "modm/src/modm/platform/i2c" @@ -64,12 +90,17 @@ def prepare(module, options): ":container", ":platform:gpio") + global_properties["shared_irqs"] = defaultdict(list) + for instance in listify(device.get_driver("i2c")["instance"]): module.add_submodule(Instance(int(instance))) return True def build(env): + env.substitutions.update(global_properties) env.outbasepath = "modm/src/modm/platform/i2c" - env.copy("i2c_timing_calculator.hpp") + env.copy("i2c_timing_calculator.hpp") + if len(global_properties["shared_irqs"]) > 0: + env.template("i2c_shared_irqs.cpp.in") From 60962a46f14780ca339c795a533f3922e74f2011 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Jun 2023 23:37:45 +0200 Subject: [PATCH 007/159] [stm32] Fix DMAMUX max channel calculation The channel ids were incorrectly sorted as strings and then converted to int. The conversion must happen first for the correct ordering. The bug has no practical effect since no device has more than 8 channels. --- src/modm/platform/dma/stm32/module.lb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modm/platform/dma/stm32/module.lb b/src/modm/platform/dma/stm32/module.lb index 4caaf4e270..0c13840264 100644 --- a/src/modm/platform/dma/stm32/module.lb +++ b/src/modm/platform/dma/stm32/module.lb @@ -5,7 +5,7 @@ # Copyright (c) 2017, Fabian Greif # Copyright (c) 2020, Mike Wolfram # Copyright (c) 2021, Raphael Lehmann -# Copyright (c) 2021, Christopher Durand +# Copyright (c) 2021-2023, Christopher Durand # # This file is part of the modm project. # @@ -129,11 +129,11 @@ def build(env): for channel in channels: instance_channels[channel["dma-instance"]].append(channel["dma-channel"]) for instance in instance_channels: - channel_list = instance_channels[instance] + channel_list = [int(c) for c in instance_channels[instance]] channel_list.sort() min_channel = channel_list[0] max_channel = channel_list[-1] - controller.append({"instance": int(instance), "min_channel": int(min_channel), "max_channel": int(max_channel)}) + controller.append({"instance": int(instance), "min_channel": min_channel, "max_channel": max_channel}) elif dma["type"] in ["stm32-stream-channel"]: for channels in dma["streams"]: max_channels = 0 From d461eee8b1b8e2db16d654f9f63984143e08a1d5 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Jun 2023 23:41:41 +0200 Subject: [PATCH 008/159] [ext] Update modm-devices submodule --- ext/modm-devices | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/modm-devices b/ext/modm-devices index 2f3262b77d..e540dc9d44 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit 2f3262b77d21acd2c89cc70bbd3d87e03fb361d1 +Subproject commit e540dc9d4430749fe0cbc20b536d71136b6f25e4 From a28177d1b57359f462f9b973a46f62f53d96dc2c Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 7 Jun 2023 00:05:34 +0200 Subject: [PATCH 009/159] [stm32] Support STM32G0B/C shared interrupt handler in SPI HAL --- src/modm/platform/spi/stm32/module.lb | 8 ++++++++ src/modm/platform/spi/stm32/spi_hal_impl.hpp.in | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/modm/platform/spi/stm32/module.lb b/src/modm/platform/spi/stm32/module.lb index 583335987d..2329a5bc84 100644 --- a/src/modm/platform/spi/stm32/module.lb +++ b/src/modm/platform/spi/stm32/module.lb @@ -3,6 +3,7 @@ # # Copyright (c) 2016-2018, Niklas Hauser # Copyright (c) 2017, Fabian Greif +# Copyright (c) 2023, Christopher Durand # # This file is part of the modm project. # @@ -37,6 +38,13 @@ class Instance(Module): properties = get_properties(env) properties["id"] = self.instance + device = env[":target"] + + irqs = [v["name"] for v in device.get_driver("core")["vector"]] + irqs = [irq for irq in irqs if irq.startswith("SPI") and str(self.instance) in irq] + assert len(irqs) == 1 + properties["irq"] = irqs[0] + env.substitutions = properties env.outbasepath = "modm/src/modm/platform/spi" diff --git a/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in index dbadb970cb..075c18d6fb 100644 --- a/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in +++ b/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in @@ -153,12 +153,12 @@ modm::platform::SpiHal{{ id }}::enableInterruptVector(bool enable, uint32_t prio { if (enable) { // Set priority for the interrupt vector - NVIC_SetPriority(SPI{{ id }}_IRQn, priority); + NVIC_SetPriority({{ irq }}_IRQn, priority); // register IRQ at the NVIC - NVIC_EnableIRQ(SPI{{ id }}_IRQn); + NVIC_EnableIRQ({{ irq }}_IRQn); } else { - NVIC_DisableIRQ(SPI{{ id }}_IRQn); + NVIC_DisableIRQ({{ irq }}_IRQn); } } From 76d2d492267f5c7fcf102cfdfb74b87720dcad12 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 7 Jun 2023 00:14:12 +0200 Subject: [PATCH 010/159] [stm32] Do not enable FDCAN driver on STM32G0 FDCAN on STM32G0 is currently not supported due to the shared interrupt handler. --- README.md | 2 +- src/modm/platform/can/stm32-fdcan/module.lb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd91b83eb5..9f63a5fcbc 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -✅ +○ ✅ ○ ✕ diff --git a/src/modm/platform/can/stm32-fdcan/module.lb b/src/modm/platform/can/stm32-fdcan/module.lb index b82a363aeb..6bcc460042 100644 --- a/src/modm/platform/can/stm32-fdcan/module.lb +++ b/src/modm/platform/can/stm32-fdcan/module.lb @@ -64,6 +64,11 @@ def prepare(module, options): if not device.has_driver("fdcan:stm32"): return False + # STM32G0 devices are currently unsupported due to shared interrupt handler + # TODO: fix driver + if device.identifier.family == "g0": + return False + module.depends( ":architecture:assert", ":architecture:atomic", From 161ec42b3a21a80c119073c7717ec0790e4c01c7 Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Tue, 6 Jun 2023 12:15:14 -0400 Subject: [PATCH 011/159] [sam] Enable cache controller for SAMD5x/E5x --- src/modm/platform/core/sam/startup_platform.c.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modm/platform/core/sam/startup_platform.c.in b/src/modm/platform/core/sam/startup_platform.c.in index 5c6c5fd1d3..6fc4fed4b1 100644 --- a/src/modm/platform/core/sam/startup_platform.c.in +++ b/src/modm/platform/core/sam/startup_platform.c.in @@ -41,4 +41,11 @@ __modm_initialize_platform(void) DMAC->QOSCTRL.bit.FQOS = 2; DMAC->QOSCTRL.bit.WRBQOS = 2; %% endif +%% if target.family == "d5x/e5x" + + // Enable cache controller + if (CMCC->SR.bit.CSTS == 0) { + CMCC->CTRL.bit.CEN = 1; + } +%%endif } From a2e095d2e238cc842857d6dc89056acaa48e4c22 Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Mon, 29 May 2023 16:03:12 -0400 Subject: [PATCH 012/159] [board] Add Adafruit Feather-M4 BSP --- README.md | 26 ++-- src/modm/board/feather_m4/board.hpp | 186 ++++++++++++++++++++++++++++ src/modm/board/feather_m4/board.xml | 15 +++ src/modm/board/feather_m4/module.lb | 45 +++++++ src/modm/board/feather_m4/module.md | 76 ++++++++++++ 5 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 src/modm/board/feather_m4/board.hpp create mode 100644 src/modm/board/feather_m4/board.xml create mode 100644 src/modm/board/feather_m4/module.lb create mode 100644 src/modm/board/feather_m4/module.md diff --git a/README.md b/README.md index 9f63a5fcbc..5c6fd1cb21 100644 --- a/README.md +++ b/README.md @@ -632,63 +632,65 @@ We have out-of-box support for many development boards including documentation. DISCO-L476VG FEATHER-M0 +FEATHER-M4 FEATHER-RP2040 MEGA-2560-PRO -NUCLEO-F031K6 +NUCLEO-F031K6 NUCLEO-F042K6 NUCLEO-F072RB NUCLEO-F091RC -NUCLEO-F103RB +NUCLEO-F103RB NUCLEO-F303K8 NUCLEO-F303RE NUCLEO-F334R8 -NUCLEO-F401RE +NUCLEO-F401RE NUCLEO-F411RE NUCLEO-F429ZI NUCLEO-F439ZI -NUCLEO-F446RE +NUCLEO-F446RE NUCLEO-F446ZE NUCLEO-F746ZG NUCLEO-F767ZI -NUCLEO-G071RB +NUCLEO-G071RB NUCLEO-G431KB NUCLEO-G431RB NUCLEO-G474RE -NUCLEO-H723ZG +NUCLEO-H723ZG NUCLEO-H743ZI NUCLEO-L031K6 NUCLEO-L053R8 -NUCLEO-L152RE +NUCLEO-L152RE NUCLEO-L432KC NUCLEO-L452RE NUCLEO-L476RG -NUCLEO-L496ZG-P +NUCLEO-L496ZG-P NUCLEO-L552ZE-Q NUCLEO-U575ZI-Q OLIMEXINO-STM32 -Raspberry Pi +Raspberry Pi Raspberry Pi Pico SAMD21-MINI SAMD21-XPLAINED-PRO -SAME54-XPLAINED-PRO +SAME54-XPLAINED-PRO SAME70-XPLAINED SAMG55-XPLAINED-PRO SAMV71-XPLAINED-ULTRA -Smart Response XE +Smart Response XE STM32-F4VE STM32F030-DEMO THINGPLUS-RP2040 + @@ -700,7 +702,7 @@ We have out-of-box support for many development boards including documentation. We also have a number of completely target-independent drivers for external devices connected via I2C, SPI, UART, BitBang, etc. Most of these also give you access to the entire device so you can easily configure them for -you specific needs. +your specific needs.
diff --git a/src/modm/board/feather_m4/board.hpp b/src/modm/board/feather_m4/board.hpp new file mode 100644 index 0000000000..8293da9c5f --- /dev/null +++ b/src/modm/board/feather_m4/board.hpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2016-2017, Sascha Schade + * Copyright (c) 2017-2018, Niklas Hauser + * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2021, Jeff McBride + * Copyright (c) 2022, Christopher Durand + * Copyright (c) 2023, Thomas Rush + * + * 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 + +#define MODM_BOARD_HAS_LOGGER + +using namespace modm::platform; + +namespace Board +{ +/// @ingroup modm_board_feather_m4 +/// @{ +using namespace modm::literals; + +/// The following pin aliases are as labeled on the board, with the following +/// exceptions: Mosi is labeled "MO"; Miso is "MI"; and the 'D' pins +/// (D4-D6, D9-D13) do not have the "D" on the silkscreen, just the numbers +/// (except D4, go figure). + +/// ARef (analog reference) is jumpered DIRECTLY (via a board trace) +/// to 3.3V, and therefore cannot be used for IO purposes. +using ARef = GpioA03; + +using A0 = GpioA02; +using A1 = GpioA05; +using A2 = GpioB08; +using A3 = GpioB09; +using A4 = GpioA04; +using A5 = GpioA06; + +using Sck = GpioA17; +using Mosi = GpioB23; +using Miso = GpioB22; + +using Rx = GpioB17; +using Tx = GpioB16; + +using D13 = GpioA23; +using D12 = GpioA22; +using D11 = GpioA21; +using D10 = GpioA20; +using D9 = GpioA19; +using D6 = GpioA18; +using D5 = GpioA16; +using D4 = GpioA14; + +using Scl = GpioA13; +using Sda = GpioA12; + +/// This is the red LED by the USB jack. +using Led = D13; + +/// The "Neopixel" is Adafruit's name for a WS2812 equivalent RGB LED. +/// There is no breakout for it. +using Neopixel = GpioB03; + +/// Alternate names for Rx and Tx. +using D0 = Rx; +using D1 = Tx; + +/// SAMD51J19A running at 120MHz generated from the 48MHz DFLL +/// divided by 48 for a 1MHz reference. +struct SystemClock +{ + static constexpr uint32_t Frequency = 120_MHz; + + static constexpr uint32_t Clock48MHz = 48_MHz; + static constexpr auto Generator48MHz = ClockGenerator::Generator1; + + static constexpr uint32_t Clock32k = 32768; + static constexpr auto Generator32k = ClockGenerator::Generator2; + + // when DFLL is in closed loop mode, the "1_MHz" DPLL reference is not + // exactly 1MHz; calculating the actual value allows 'enableDpll()' to + // check the tolerance + static constexpr uint32_t Clock1MHz = ((48_MHz / 32768) * 32768) / 48; + static constexpr auto Generator1MHz = ClockGenerator::Generator3; + + static constexpr uint32_t Usb = Clock48MHz; + + static constexpr uint32_t Sercom5 = Frequency; + static constexpr uint32_t SercomSlow = Clock32k; + + static bool inline + enable() + { + // start the 32kHz crystal oscillator and connect to generator + GenericClockController::enableExternalCrystal32k(Xosc32StartupTime::Start_500ms); + GenericClockController::enableGenerator(); + + // set up Xosc32k as DFLL reference + GenericClockController::connect(Generator32k); + + // while restarting DFLL, run system temporarily at 32kHz + GenericClockController::enableGenerator(); + + // restart DFLL in closed loop + GenericClockController::enableDfll48mClosedLoop(); + + // get system back up to speed + GenericClockController::enableGenerator(); + + // set up a 1MHz reference for DPLL0 + GenericClockController::enableGenerator(); + GenericClockController::connect(Generator1MHz); + + // start DPLL0 + GenericClockController::enableDpll, Frequency>(); + + // make it the system clock + GenericClockController::setFlashLatency(); + GenericClockController::updateCoreFrequency(); + GenericClockController::setSystemClock(); + + GenericClockController::enableGenerator(); + + GenericClockController::connect(Generator48MHz); + + GenericClockController::connect(ClockGenerator::System); + GenericClockController::connect(Generator32k); + + return true; + } +}; + +using Leds = SoftwareGpioPort; + +struct Debug +{ + using Uart = Uart5; + using UartTx = Tx; + using UartRx = Rx; +}; + +using LoggerDevice = modm::IODeviceWrapper; + +inline void +initialize() +{ + SystemClock::enable(); + SysTickTimer::initialize(); + + Debug::Uart::initialize(); + Debug::Uart::connect(); + + Leds::setOutput(modm::Gpio::Low); + + // The Neopixel is normally set to black (off) by the bootloader after a + // new program is uploaded to flash; but if a program is running and the + // reset button is pressed while the Neopixel is lit, it will stay on with + // the same color. + // + // Setting the output to low does not turn it off, it merely ensures that + // the signal is in the idle state. Providing a driver in the board module + // just to turn it off is unnecessary. + Neopixel::setOutput(modm::Gpio::Low); +} + +inline void +initializeUsbFs() +{ + modm::platform::Usb::initialize(); + modm::platform::Usb::connect(); +} + +/// @} + +} // namespace Board diff --git a/src/modm/board/feather_m4/board.xml b/src/modm/board/feather_m4/board.xml new file mode 100644 index 0000000000..caa36a22e3 --- /dev/null +++ b/src/modm/board/feather_m4/board.xml @@ -0,0 +1,15 @@ + + + + ../../../../repo.lb + + + + + + + + + modm:board:feather-m4 + + diff --git a/src/modm/board/feather_m4/module.lb b/src/modm/board/feather_m4/module.lb new file mode 100644 index 0000000000..fac8734ea3 --- /dev/null +++ b/src/modm/board/feather_m4/module.lb @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2020, Erik Henriksson +# Copyright (c) 2021, Jeff McBride +# Copyright (c) 2021-2022, Christopher Durand +# Copyright (c) 2023, Thomas Rush +# +# 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:feather-m4" + module.description = FileReader("module.md") + +def prepare(module, options): + if not options[":target"].partname == "samd51j19a-au": + return False + + module.depends( + ":debug", + ":platform:gclk", + ":platform:gpio", + ":platform:core", + ":platform:usb", + ":platform:uart:5") + + return True + +def build(env): + env.outbasepath = "modm/src/modm/board" + env.substitutions = { + "with_logger": True, + "with_assert": env.has_module(":architecture:assert"), + } + env.template("../board.cpp.in", "board.cpp") + env.copy('.') + + diff --git a/src/modm/board/feather_m4/module.md b/src/modm/board/feather_m4/module.md new file mode 100644 index 0000000000..1de3c10b2c --- /dev/null +++ b/src/modm/board/feather_m4/module.md @@ -0,0 +1,76 @@ +# Adafruit Feather M4 Express + +The Feather M4 Express features an ATSAMD51J19 (a 120MHz Cortex M4 with +floating point support, 512KB Flash and 192KB RAM), plus a number of extra +peripherals for support. + +[Main Product Page](https://www.adafruit.com/product/3857) + +[User Guide](https://learn.adafruit.com/adafruit-feather-m4-express-atsamd51) + +## Board Details + +### Pins available +The Feather boards have dimensions of 51mm x 23mm for a fairly compact form +factor. That does, however, limit the number of pins broken out on the board. + +Out of 51 GPIO pins on the SAMD51J19, only 22 have pads for the breakout headers. +One of these, labeled AREF (PA03), is connected directly to 3.3V and thus cannot +be used for IO. (This is due to a problem with the chip itself; the DAC will not +function without this connection. If the DAC is not needed, the trace can be +cut and the pin used as desired.) + +There are seven other pins not broken out but used for devices on-board: +six are used for a 2MB QSPI Flash, and one for the 'Neopixel', Adafruit's +name for a WS2812 RGB LED. + +Pins PA30-PA31 have pads on the underside of the board for the Serial Wire +Debug Interface (SWCLK and SWDIO, respectively), allowing connection to a +debug probe. + +The [pinouts](https://learn.adafruit.com/adafruit-feather-m4-express-atsamd51/pinouts) +page in the User Guide has further information, photos, and a useful diagram +showing the various pin functions. (This diagram does have some errors, however; +the most noticeable being the labeling of several pins as outputs for TC6 & TC7, +Timer/Counters which are not present on the chip.) + +### Board labeling + +Most pins broken out on the board are labeled Arduino-style, with A0-A5 having +'analog' functions, and 0-13 (not all of which are available) for 'digital'. +A few pins are labeled for specific functions, e.g. SPI, I2C, etc. All pins +are aliased in 'board.hpp' to their GPIO equivalents; the 'digital' (0-13) +pin aliases are prefixed with a 'D'. + +## Clocks + +Although the SAMD51 has pins available for crystal oscillators XOSC1 and XOSC2, +there are no crystals installed on this board for their use. There is, however, +a 32kHz crystal for the XOSC32K oscillator. + +Typically, an FDPLL is used as the system clock after bootup is complete; and +using XOSC32K as a reference for it is not reliable. Even when using the workaround +prescibed in Item 2.13 of the [Errata](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU32/ProductDocuments/Errata/SAM-D5x-E5x-Family-Silicon-Errata-and-Data-Sheet-Clarification-DS80000748.pdf), +the result is usually failure. + +The simplest solution is to source a GCLK generator with the 48MHz DFLL, and +use a division factor that creates a suitable reference frequency for the FDPLL +(e.g. 1-2MHz). It can be done with the DFLL in open- or closed-loop mode, but +in order to meet the USB timing specs, closed-loop with XOSC32K as reference is +used here. + +## Programming + +The Feather M4 has a [UF2](https://github.com/microsoft/uf2) bootloader. By +using a utility to convert the '.elf' files (generated by scons or make) to +'.uf2' files, it is then possible to copy the uf2 file directly to the board. + +One of the more common tools for this conversion is ['uf2conv.py'](https://github.com/microsoft/uf2/tree/master/utils) (although the elf file needs to be converted to a +binary or hex file first). The converter that is supplied with modm, 'elf2uf2.py', +is ONLY for the rp2040, and will not work here. + +However, the bootloader on the Feather is compatible with [BOSSA](https://www.shumatech.com/web/products/bossa); so the easiest method when working with modm is to use +'scons program-bossac' or 'make program-bossac' to load your program into +flash. (Be sure to press the reset button twice to get the board into bootloader +mode before flashing.) + From fe3c24e899c8d7a27d7f7212ad7f7d3120c7eb4e Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Mon, 29 May 2023 16:07:30 -0400 Subject: [PATCH 013/159] [example] Add Feather M4 blink example --- .github/workflows/linux.yml | 2 +- examples/feather_m4/blink/main.cpp | 40 +++++++++++++++++++++++++++ examples/feather_m4/blink/project.xml | 10 +++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 examples/feather_m4/blink/main.cpp create mode 100644 examples/feather_m4/blink/project.xml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1f2431f858..d6e4765bc7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -92,7 +92,7 @@ jobs: - name: Examples SAMD Devices if: always() run: | - (cd examples && ../tools/scripts/examples_compile.py samd samd21_xplained_pro) + (cd examples && ../tools/scripts/examples_compile.py feather_m4 samd samd21_xplained_pro) - name: Examples SAMG Devices if: always() run: | diff --git a/examples/feather_m4/blink/main.cpp b/examples/feather_m4/blink/main.cpp new file mode 100644 index 0000000000..e7c6a21108 --- /dev/null +++ b/examples/feather_m4/blink/main.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016-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; + +int +main() +{ + 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; + + uint32_t counter(0); + + while (true) + { + Led::toggle(); + modm::delay(500ms); + + MODM_LOG_INFO << "loop: " << counter++ << modm::endl; + } + + return 0; +} + diff --git a/examples/feather_m4/blink/project.xml b/examples/feather_m4/blink/project.xml new file mode 100644 index 0000000000..96e9674f07 --- /dev/null +++ b/examples/feather_m4/blink/project.xml @@ -0,0 +1,10 @@ + + modm:feather-m4 + + + + + modm:build:scons + modm:architecture:delay + + From b15034a6bd63ada6cfaddb483ff3b4086c636353 Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Mon, 29 May 2023 16:08:19 -0400 Subject: [PATCH 014/159] [example] Add Feather M4 neopixel example --- examples/feather_m4/neopixel/main.cpp | 94 ++++++++++++++++++++++++ examples/feather_m4/neopixel/project.xml | 10 +++ 2 files changed, 104 insertions(+) create mode 100644 examples/feather_m4/neopixel/main.cpp create mode 100644 examples/feather_m4/neopixel/project.xml diff --git a/examples/feather_m4/neopixel/main.cpp b/examples/feather_m4/neopixel/main.cpp new file mode 100644 index 0000000000..46c9877ecf --- /dev/null +++ b/examples/feather_m4/neopixel/main.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2017, Niklas Hauser + * Copyright (c) 2023, Thomas Rush + * + * 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; + +/* + * Output a color (GRB) to the Neopixel + * + * 'Neopixel' is Adafruit's name for what is essentially a WS2812 RGB LED. + * The timing, which runs at 800kHz, is basically a long high pulse followed + * by a short low pulse for a '1' bit, or a short high pulse followed by a + * long low pulse for a '0' bit. The idle state of the signal is low. The + * data is 24 bits long (three 8-bit color values sent out in green-red-blue + * order, MSB first). + * + * The long and short pulse times chosen here are nominally 875ns and 375ns, + * respectively, totalling 1.25us per bit. Using modm::delay_ns() with these + * values means that the actual times will be slightly longer because of the + * setup times for each bit, but the cycle time can be longer (within limits) + * without affecting the operation. (See + * https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ + * for more information about the signal timing for these devices.) + * + * This function is not intended to be the best solution as a driver for the + * WS2812, just an example of a simple application of the modm library. + */ + +void +setPixel(uint32_t data) +{ + constexpr uint32_t neoPeriod = 1250; + constexpr uint32_t neoOneHi = 875; + constexpr uint32_t neoZeroHi = 375; + + uint32_t bitMask = (1UL << 23); + + { + modm::atomic::Lock lock; + + do + { + uint32_t hiTime = data & bitMask ? neoOneHi : neoZeroHi; + Neopixel::toggle(); // toggle high + modm::delay_ns(hiTime); + Neopixel::toggle(); // toggle low + modm::delay_ns(neoPeriod - hiTime); + bitMask >>= 1; + } + while (bitMask); + } +} + +int +main() +{ + 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; + + uint32_t counter(0); + + const char *log_str[] = { "green ", "red ", "blue ", "loop: " }; + + while (true) + { + // color values are reduced to decrease the brightness of the LED + // (full intensity can be uncomfortable for the eyes!) + for (uint32_t i = 0, color = 0x110000; i < 4; i++, color >>= 8) { + modm::delay(1s); + setPixel(color); + MODM_LOG_INFO << log_str[i]; + } + MODM_LOG_INFO << counter++ << modm::endl; + } + + return 0; +} + diff --git a/examples/feather_m4/neopixel/project.xml b/examples/feather_m4/neopixel/project.xml new file mode 100644 index 0000000000..be177c7bb7 --- /dev/null +++ b/examples/feather_m4/neopixel/project.xml @@ -0,0 +1,10 @@ + + modm:feather-m4 + + + + + modm:build:scons + modm:architecture:delay + + From 81b86be9f6d9539a627b0f07c90c2659928ef2b3 Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Mon, 29 May 2023 16:10:20 -0400 Subject: [PATCH 015/159] [example] Add Feather M4 usbserial example --- examples/feather_m4/usbserial/main.cpp | 51 +++++++++++++++++++++++ examples/feather_m4/usbserial/project.xml | 12 ++++++ 2 files changed, 63 insertions(+) create mode 100644 examples/feather_m4/usbserial/main.cpp create mode 100644 examples/feather_m4/usbserial/project.xml diff --git a/examples/feather_m4/usbserial/main.cpp b/examples/feather_m4/usbserial/main.cpp new file mode 100644 index 0000000000..55d917ca77 --- /dev/null +++ b/examples/feather_m4/usbserial/main.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-2017, Niklas Hauser + * Copyright (c) 2022, Christopher Durand + * Copyright (c) 2023, Thomas Rush + * + * 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 + +using namespace Board; + +modm::IODeviceWrapper usb_io_device; +modm::IOStream usb_stream(usb_io_device); + +int +main() +{ + initialize(); + initializeUsbFs(); + + tusb_init(); + + uint32_t counter(0); + + modm::PeriodicTimer timer{500ms}; + + while (not timer.execute()) { + tud_task(); + } + + // it can be hard to catch this without the preceding delay + usb_stream << "Hello from USB" << modm::endl; + + while (true) + { + tud_task(); + if (timer.execute()) { + Led::toggle(); + usb_stream << "loop: " << counter++ << modm::endl; + } + } + + return 0; +} diff --git a/examples/feather_m4/usbserial/project.xml b/examples/feather_m4/usbserial/project.xml new file mode 100644 index 0000000000..ccf9dc7e8b --- /dev/null +++ b/examples/feather_m4/usbserial/project.xml @@ -0,0 +1,12 @@ + + modm:feather-m4 + + + + + + modm:tinyusb + modm:build:scons + modm:processing:timer + + From 3632f017129ff72de9ae1337e69edfc752db6863 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 8 Jun 2023 15:26:49 +0200 Subject: [PATCH 016/159] [cortex] Remove -fsingle-precision-constant default option --- src/modm/platform/core/cortex/module.lb | 3 +-- src/modm/platform/core/cortex/module.md | 12 ++++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb index 3c7f37d88a..75bf17cb34 100644 --- a/src/modm/platform/core/cortex/module.lb +++ b/src/modm/platform/core/cortex/module.lb @@ -370,5 +370,4 @@ def build(env): env.collect(":build:archflags", "-mfloat-abi={}".format(env["float-abi"]), fpu_spec) single_precision = ("-sp-" in fpu_spec) if single_precision: - env.collect(":build:ccflags", "-fsingle-precision-constant", - "-Wdouble-promotion") + env.collect(":build:ccflags", "-Wdouble-promotion") diff --git a/src/modm/platform/core/cortex/module.md b/src/modm/platform/core/cortex/module.md index 9118f8597a..8b467e4d9d 100644 --- a/src/modm/platform/core/cortex/module.md +++ b/src/modm/platform/core/cortex/module.md @@ -327,8 +327,15 @@ This module adds these architecture specific [compiler options][options]: - `-mthumb`: only Thumb2 instruction set is supported. - `-mfloat-abi={soft, softfp, hard}`: the FPU ABI: `hard` is fastest. - `-mfpu=fpv{4, 5}-{sp}-d16`: single or double precision FPU. -- `-fsingle-precision-constant`: if SP-FPU, treat all FP constants as SP. -- `-Wdouble-promotion`: if SP-FPU, warn if FPs are promoted to doubles. +- `-Wdouble-promotion`: if SP-FPU, warn if FPs are promoted to doubles. Note + that unless you use the `.f` suffix or explicitly cast floating point + operations to `float`, floating point constants are of `double` type, whose + storage can result in an increased binary size. While you can add the + `-fsingle-precision-constant` compiler flag to implicitly cast all doubles to + floats, this also impacts compile time computations and may reduce accuracy. + Therefore it is not enabled by default and you should carefully watch for any + unwanted numeric side effects if you use this compiler option. + See [Semantics of Floating Point Math in GCC][gcc_math]. In addition, these linker options are added: @@ -336,3 +343,4 @@ In addition, these linker options are added: - `-wrap,_{calloc, malloc, realloc, free}_r`: reimplemented Newlib with our own allocator. [options]: https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html +[gcc_math]: https://gcc.gnu.org/wiki/FloatingPointMath From cd584b1b506f78f2396f6cf8bb267357587ab836 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 9 Jun 2023 14:05:36 +0200 Subject: [PATCH 017/159] [src] Fix warnings about double promotion --- examples/nucleo_f042k6/tmp12x/main.cpp | 2 +- .../temperature_mcp990x/main.cpp | 2 +- examples/nucleo_g474re/adc_basic/main.cpp | 2 +- examples/nucleo_g474re/ads7828/main.cpp | 40 +++++++++---------- examples/nucleo_g474re/imu_lsm6dso/main.cpp | 2 +- .../timer_input_capture/main.cpp | 6 +-- examples/nucleo_l552ze-q/adc_basic/main.cpp | 2 +- .../stm32f3_discovery/accelerometer/main.cpp | 8 ++-- .../stm32f4_discovery/accelerometer/main.cpp | 8 ++-- src/modm/math/geometry/angle.cpp | 16 ++++---- src/modm/math/geometry/angle.hpp | 5 ++- .../platform/can/common/can_bit_timings.hpp | 2 +- src/modm/ui/color/brightness.hpp | 4 +- 13 files changed, 50 insertions(+), 49 deletions(-) diff --git a/examples/nucleo_f042k6/tmp12x/main.cpp b/examples/nucleo_f042k6/tmp12x/main.cpp index 3be43dbfa6..0b30ebf4b3 100644 --- a/examples/nucleo_f042k6/tmp12x/main.cpp +++ b/examples/nucleo_f042k6/tmp12x/main.cpp @@ -44,7 +44,7 @@ main() while(true) { if (timer.execute()) { const modm::Tmp123Temperature temperature = RF_CALL_BLOCKING(sensor.read()); - MODM_LOG_INFO.printf("Temperature %2.2f\n", temperature.getTemperatureFloat()); + MODM_LOG_INFO.printf("Temperature %2.2f\n", (double)temperature.getTemperatureFloat()); } } } diff --git a/examples/nucleo_f303re/temperature_mcp990x/main.cpp b/examples/nucleo_f303re/temperature_mcp990x/main.cpp index 9f2f3631a0..17f3cb7abc 100644 --- a/examples/nucleo_f303re/temperature_mcp990x/main.cpp +++ b/examples/nucleo_f303re/temperature_mcp990x/main.cpp @@ -50,7 +50,7 @@ int main() { if (RF_CALL_BLOCKING(sensor.readInternalTemperature())) { - MODM_LOG_INFO.printf("temperature: %3.3f\n °C\n", data.getTemperature()); + MODM_LOG_INFO.printf("temperature: %3.3f\n °C\n", (double)data.getTemperature()); } else { diff --git a/examples/nucleo_g474re/adc_basic/main.cpp b/examples/nucleo_g474re/adc_basic/main.cpp index 2d5357c674..ec73913491 100644 --- a/examples/nucleo_g474re/adc_basic/main.cpp +++ b/examples/nucleo_g474re/adc_basic/main.cpp @@ -42,7 +42,7 @@ main() MODM_LOG_INFO << "adcValue=" << adcValue; float voltage = adcValue * 3.3 / 0xfff; MODM_LOG_INFO << " voltage="; - MODM_LOG_INFO.printf("%.3f\n", voltage); + MODM_LOG_INFO.printf("%.3f\n", (double)voltage); modm::delay(500ms); } diff --git a/examples/nucleo_g474re/ads7828/main.cpp b/examples/nucleo_g474re/ads7828/main.cpp index 15276dcc4e..6cdccd7f6a 100644 --- a/examples/nucleo_g474re/ads7828/main.cpp +++ b/examples/nucleo_g474re/ads7828/main.cpp @@ -47,84 +47,84 @@ class AdcThread : public modm::pt::Protothread MODM_LOG_INFO << "-------------------------------" << modm::endl << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch0 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch0 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch1)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch1 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch1 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch2)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch2 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch2 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch3)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch3 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch3 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch4)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch4 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch4 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch5)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch5 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch5 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch6)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch6 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch6 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch7)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch7 measuremnt is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch7 measuremnt is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; MODM_LOG_INFO << "----Diff Inputs-------------" << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0Ch1)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch0 - Ch1 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch0 - Ch1 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch2Ch3)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch2 - Ch3 is\t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch2 - Ch3 is\t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch4Ch5)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch4 - Ch5 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch4 - Ch5 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch6Ch7)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch6 - Ch7 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch6 - Ch7 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch1Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch1 - Ch0 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch1 - Ch0 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch3Ch2)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch3 - Ch2 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch3 - Ch2 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch5Ch4)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch5 - Ch4 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch5 - Ch4 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch7Ch6)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Ch7 - Ch6 is \t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Ch7 - Ch6 is \t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; MODM_LOG_INFO << "---Toggling Power Down and Internal Ref----" << modm::endl; @@ -132,25 +132,25 @@ class AdcThread : public modm::pt::Protothread PT_CALL(adc.setPowerDownSelection(modm::ads7828::PowerDown::InternalReferenceOffAdcConverterOff)); PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Default: \t\t\t\t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("Default: \t\t\t\t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.setPowerDownSelection(modm::ads7828::PowerDown::InternalReferenceOnAdcConverterOff)); PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Internal ref on: \t\t\t %.4f", data.getVoltage(2.5f)); + MODM_LOG_INFO.printf("Internal ref on: \t\t\t %.4f", (double)data.getVoltage(2.5f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.setPowerDownSelection(modm::ads7828::PowerDown::InternalReferenceOffAdcConverterOn)); PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("No power down \t\t\t\t %.4f", data.getVoltage(3.3f)); + MODM_LOG_INFO.printf("No power down \t\t\t\t %.4f", (double)data.getVoltage(3.3f)); MODM_LOG_INFO << modm::endl; PT_CALL(adc.setPowerDownSelection(modm::ads7828::PowerDown::InternalReferenceOnAdcConverterOn)); PT_CALL(adc.startMeasurement(modm::ads7828::InputChannel::Ch0)); PT_CALL(adc.readConversionResult()); - MODM_LOG_INFO.printf("Internal ref on, no power down: \t %.4f", data.getVoltage(2.5f)); + MODM_LOG_INFO.printf("Internal ref on, no power down: \t %.4f", (double)data.getVoltage(2.5f)); MODM_LOG_INFO << modm::endl; MODM_LOG_INFO << "-------------------------------" << modm::endl << modm::endl; diff --git a/examples/nucleo_g474re/imu_lsm6dso/main.cpp b/examples/nucleo_g474re/imu_lsm6dso/main.cpp index ffdc3b08b2..e4f80e360c 100644 --- a/examples/nucleo_g474re/imu_lsm6dso/main.cpp +++ b/examples/nucleo_g474re/imu_lsm6dso/main.cpp @@ -103,7 +103,7 @@ main() } // print data anyways - MODM_LOG_INFO.printf("TEMP=%+2.1f degC, ", (static_cast(data[0] | data[1] << 8) / 256.f) + 25.f); + MODM_LOG_INFO.printf("TEMP=%+2.1f degC, ", (double)((static_cast(data[0] | data[1] << 8) / 256.f) + 25.f)); MODM_LOG_INFO.printf("G_X=%+05i, ", static_cast(data[ 2] | data[ 3] << 8)); MODM_LOG_INFO.printf("G_Y=%+05i, ", static_cast(data[ 4] | data[ 5] << 8)); MODM_LOG_INFO.printf("G_Z=%+05i, ", static_cast(data[ 6] | data[ 7] << 8)); diff --git a/examples/nucleo_g474re/timer_input_capture/main.cpp b/examples/nucleo_g474re/timer_input_capture/main.cpp index 1ed17af633..f55a7c09d6 100644 --- a/examples/nucleo_g474re/timer_input_capture/main.cpp +++ b/examples/nucleo_g474re/timer_input_capture/main.cpp @@ -25,7 +25,7 @@ using namespace Board; constexpr uint16_t input_capture_overflow = 0xFFFF; constexpr float input_capture_freq_hz = 3000.00; // Hz constexpr uint16_t input_capture_prescaler = SystemClock::Frequency / input_capture_freq_hz; -constexpr float input_capture_ms_per_tick = ( 1.0 / input_capture_freq_hz ) * 1000.0; +constexpr float input_capture_ms_per_tick = ( 1.0f / input_capture_freq_hz ) * 1000.0; // PWM Generator Timer Configuration constexpr uint16_t pwm_gen_overflow = 0xFFFF; @@ -36,7 +36,7 @@ constexpr float pwm_gen_pulse_width_ms = 1.5f; // Milliseconds // Desired PWM Freq = Clock Freq / Timer Prescaler / Overflow Value // Timer Prescaler = Clock Freq / (Desired PWM Freq * Overflow Value) constexpr uint16_t pwm_gen_prescaler = SystemClock::Frequency / ( pwm_gen_frequency_hz * pwm_gen_overflow ); -constexpr float pwm_gen_period_ms = ( 1.0 / pwm_gen_frequency_hz ) * 1000; +constexpr float pwm_gen_period_ms = ( 1.0f / pwm_gen_frequency_hz ) * 1000; constexpr uint16_t pwm_gen_ticks_per_period = pwm_gen_overflow / pwm_gen_period_ms; constexpr uint16_t pwm_pulse_width_in_ticks = pwm_gen_ticks_per_period * pwm_gen_pulse_width_ms; // -------------------------------------------------------------------------- @@ -175,4 +175,4 @@ int main() } return 0; -} \ No newline at end of file +} diff --git a/examples/nucleo_l552ze-q/adc_basic/main.cpp b/examples/nucleo_l552ze-q/adc_basic/main.cpp index eb0cc9ea61..6cbb606da7 100644 --- a/examples/nucleo_l552ze-q/adc_basic/main.cpp +++ b/examples/nucleo_l552ze-q/adc_basic/main.cpp @@ -44,7 +44,7 @@ main() MODM_LOG_INFO << "adcValue=" << adcValue; const float voltage = adcValue * 3.3 / 0xfff; MODM_LOG_INFO << " voltage="; - MODM_LOG_INFO.printf("%.3f\n", voltage); + MODM_LOG_INFO.printf("%.3f\n", (double)voltage); Board::Leds::toggle(); modm::delay(500ms); } diff --git a/examples/stm32f3_discovery/accelerometer/main.cpp b/examples/stm32f3_discovery/accelerometer/main.cpp index 86cbbb5504..21658e0dd5 100644 --- a/examples/stm32f3_discovery/accelerometer/main.cpp +++ b/examples/stm32f3_discovery/accelerometer/main.cpp @@ -42,11 +42,11 @@ class ReaderThread : public modm::pt::Protothread averageY.update(accelerometer.getData().getY()); { - bool xs = averageX.getValue() < -0.2; - bool xn = averageX.getValue() > 0.2; + bool xs = averageX.getValue() < -0.2f; + bool xn = averageX.getValue() > 0.2f; - bool xe = averageY.getValue() < -0.2; - bool xw = averageY.getValue() > 0.2; + bool xe = averageY.getValue() < -0.2f; + bool xw = averageY.getValue() > 0.2f; Board::LedNorth::set(xn and not (xe or xw)); Board::LedNorthEast::set(xn and xe); diff --git a/examples/stm32f4_discovery/accelerometer/main.cpp b/examples/stm32f4_discovery/accelerometer/main.cpp index 84067debc0..098b8fbab9 100644 --- a/examples/stm32f4_discovery/accelerometer/main.cpp +++ b/examples/stm32f4_discovery/accelerometer/main.cpp @@ -87,10 +87,10 @@ class ReaderThread : public modm::pt::Protothread averageY.update(accel.getData().getY()); #endif - Board::LedOrange::set(averageX.getValue() < -0.2); - Board::LedBlue::set(averageX.getValue() > 0.2); - Board::LedGreen::set(averageY.getValue() < -0.2); - Board::LedRed::set(averageY.getValue() > 0.2); + Board::LedOrange::set(averageX.getValue() < -0.2f); + Board::LedBlue::set(averageX.getValue() > 0.2f); + Board::LedGreen::set(averageY.getValue() < -0.2f); + Board::LedRed::set(averageY.getValue() > 0.2f); timeout.restart(5ms); PT_WAIT_UNTIL(timeout.isExpired()); diff --git a/src/modm/math/geometry/angle.cpp b/src/modm/math/geometry/angle.cpp index c001caf9f8..307482f6d6 100644 --- a/src/modm/math/geometry/angle.cpp +++ b/src/modm/math/geometry/angle.cpp @@ -22,13 +22,13 @@ modm::Angle::normalize(float angle) { if (isPositive(angle)) { - while (angle > M_PI) { - angle -= 2 * M_PI; + while (angle > std::numbers::pi_v) { + angle -= 2 * std::numbers::pi_v; } } else { - while (angle < -M_PI) { - angle += 2 * M_PI; + while (angle < -std::numbers::pi_v) { + angle += 2 * std::numbers::pi_v; } } @@ -41,10 +41,10 @@ modm::Angle::reverse(float angle) { if (isPositive(angle)) { - angle -= M_PI; + angle -= std::numbers::pi_v; } else { - angle += M_PI; + angle += std::numbers::pi_v; } return angle; @@ -56,10 +56,10 @@ modm::Angle::perpendicular(float angle, const bool cw) { if (cw) { - angle = modm::Angle::normalize(angle - M_PI_2); + angle = modm::Angle::normalize(angle - std::numbers::pi_v/2); } else { - angle = modm::Angle::normalize(angle + M_PI_2); + angle = modm::Angle::normalize(angle + std::numbers::pi_v/2); } return angle; diff --git a/src/modm/math/geometry/angle.hpp b/src/modm/math/geometry/angle.hpp index 76a9147253..307c950338 100644 --- a/src/modm/math/geometry/angle.hpp +++ b/src/modm/math/geometry/angle.hpp @@ -18,6 +18,7 @@ #include #include +#include // The circumference of a circle with diameter 1 #ifndef M_PI @@ -55,14 +56,14 @@ namespace modm static constexpr float toRadian(float angle) { - return (angle * M_PI) / 180.f; + return (angle * std::numbers::pi_v) / 180.f; } /// @ingroup modm_math_geometry static constexpr float toDegree(float angle) { - return (angle * 180.f) / M_PI; + return (angle * 180.f) / std::numbers::pi_v; } /** diff --git a/src/modm/platform/can/common/can_bit_timings.hpp b/src/modm/platform/can/common/can_bit_timings.hpp index e78689dd3a..57cfcc8dd7 100644 --- a/src/modm/platform/can/common/can_bit_timings.hpp +++ b/src/modm/platform/can/common/can_bit_timings.hpp @@ -63,7 +63,7 @@ class CanBitTiming static constexpr uint32_t round_uint32(float f) { uint32_t f_int = (uint32_t) f; - if(f - f_int > 0.5) + if(f - f_int > 0.5f) return f_int + 1; else return f_int; diff --git a/src/modm/ui/color/brightness.hpp b/src/modm/ui/color/brightness.hpp index bc5e961cb5..e82fa98e24 100644 --- a/src/modm/ui/color/brightness.hpp +++ b/src/modm/ui/color/brightness.hpp @@ -75,8 +75,8 @@ class BrightnessT */ template constexpr BrightnessT(RgbT rgb) - : value((0.2125 * float(rgb.red)) + (0.7154 * float(rgb.green)) + - (0.0721 * float(rgb.blue))) + : value((0.2125f * float(rgb.red)) + (0.7154f * float(rgb.green)) + + (0.0721f * float(rgb.blue))) {} /** From 78ca76f27f1b484bee3a78b9b8dead5c8fb93953 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 9 Jun 2023 14:06:17 +0200 Subject: [PATCH 018/159] [driver] Fix cast to bool narrowing conversion warning --- src/modm/driver/inertial/bno055.hpp | 2 +- src/modm/driver/position/vl6180_impl.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modm/driver/inertial/bno055.hpp b/src/modm/driver/inertial/bno055.hpp index b4abbd387e..2cf1f4cfca 100644 --- a/src/modm/driver/inertial/bno055.hpp +++ b/src/modm/driver/inertial/bno055.hpp @@ -657,7 +657,7 @@ class Bno055 : public bno055, public modm::I2cDevice this->transaction.configureWrite(buffer, 2); buffer[2] = RF_CALL( this->runTransaction() ); if (buffer[2]) prev_reg = reg; - RF_RETURN(buffer[2]); + RF_RETURN((bool)buffer[2]); } RF_END_RETURN(true); diff --git a/src/modm/driver/position/vl6180_impl.hpp b/src/modm/driver/position/vl6180_impl.hpp index a5417b444a..7d91c973ff 100644 --- a/src/modm/driver/position/vl6180_impl.hpp +++ b/src/modm/driver/position/vl6180_impl.hpp @@ -58,7 +58,7 @@ modm::Vl6180::initialize() if (!logicBuffer.byte[0]) RF_RETURN(false); } - RF_END_RETURN(logicBuffer.byte[0]); + RF_END_RETURN((bool)logicBuffer.byte[0]); } template < typename I2cMaster > From 2384756349c3ab6d98efd9f87baa6c99e5a1dac9 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 9 Jun 2023 14:06:32 +0200 Subject: [PATCH 019/159] [board] Pass priority to USB initializer --- src/modm/board/disco_f746ng/board.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modm/board/disco_f746ng/board.hpp b/src/modm/board/disco_f746ng/board.hpp index 7e536a65bf..ce09a92b12 100644 --- a/src/modm/board/disco_f746ng/board.hpp +++ b/src/modm/board/disco_f746ng/board.hpp @@ -208,7 +208,7 @@ initialize() inline void initializeUsbFs(uint8_t priority=3) { - usb_fs::Device::initialize(); + usb_fs::Device::initialize(priority); usb_fs::Device::connect(); USB_OTG_DeviceTypeDef *dev = (USB_OTG_DeviceTypeDef *) (USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); @@ -223,9 +223,9 @@ initializeUsbFs(uint8_t priority=3) } inline void -initializeUsbHs() +initializeUsbHs(uint8_t priority=3) { - usb_hs::Device::initialize(); + usb_hs::Device::initialize(priority); usb_hs::Device::connect< usb_hs::Ck::Ulpick, usb_hs::Stp::Ulpistp, usb_hs::Dir::Ulpidir, usb_hs::Nxt::Ulpinxt, From 07d4b7f59389f68c61c06d32d0cab6ab08ff0d63 Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Fri, 16 Jun 2023 17:45:26 -0400 Subject: [PATCH 020/159] [build] Fixed '0x0x' prefix for UF2 memory map --- tools/build_script_generator/make/module.lb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build_script_generator/make/module.lb b/tools/build_script_generator/make/module.lb index 387ffe6c32..981e0d3e81 100644 --- a/tools/build_script_generator/make/module.lb +++ b/tools/build_script_generator/make/module.lb @@ -52,7 +52,7 @@ 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"]), + subs["uf2mem"] = ["{:#x}:{:#x}:{}".format(m["start"], m["start"] + m["size"], "CONTENTS" if "flash" in m["name"] else "NO_CONTENTS") for m in subs["memories"]] else: From b05df3eebb2daab9e8caf86cf5a46856ec1367eb Mon Sep 17 00:00:00 2001 From: Thomas Rush Date: Thu, 15 Jun 2023 20:24:27 -0400 Subject: [PATCH 021/159] [build] Add STM32 and SAM support to ELF2UF2 tool --- src/modm/board/feather_m4/module.md | 20 +- tools/modm_tools/elf2uf2.py | 299 +++++++++++++++++----------- 2 files changed, 195 insertions(+), 124 deletions(-) diff --git a/src/modm/board/feather_m4/module.md b/src/modm/board/feather_m4/module.md index 1de3c10b2c..402fb1d4ee 100644 --- a/src/modm/board/feather_m4/module.md +++ b/src/modm/board/feather_m4/module.md @@ -65,12 +65,16 @@ The Feather M4 has a [UF2](https://github.com/microsoft/uf2) bootloader. By using a utility to convert the '.elf' files (generated by scons or make) to '.uf2' files, it is then possible to copy the uf2 file directly to the board. -One of the more common tools for this conversion is ['uf2conv.py'](https://github.com/microsoft/uf2/tree/master/utils) (although the elf file needs to be converted to a -binary or hex file first). The converter that is supplied with modm, 'elf2uf2.py', -is ONLY for the rp2040, and will not work here. - -However, the bootloader on the Feather is compatible with [BOSSA](https://www.shumatech.com/web/products/bossa); so the easiest method when working with modm is to use -'scons program-bossac' or 'make program-bossac' to load your program into -flash. (Be sure to press the reset button twice to get the board into bootloader -mode before flashing.) +In modm, the conversion utility is called 'elf2uf2.py' (in tools/modm_tools), +and is incorporated into the build system. Use 'scons uf2' or 'make uf2' to +create the uf2 file. + +Alternatively, the bootloader on the Feather is compatible with [BOSSA](https://www.shumatech.com/web/products/bossa); +so another method is to use 'scons program-bossac' or 'make program-bossac' to +load your program into flash. + +With either method, you must press the reset button twice to get the board into +bootloader mode before flashing with BOSSA or UF2 copying. (In bootloader mode, +a volume named FEATHERBOOT will be mounted; and that is where the UF2 file +should be copied.) diff --git a/tools/modm_tools/elf2uf2.py b/tools/modm_tools/elf2uf2.py index b6cfec10b8..f36087a0da 100755 --- a/tools/modm_tools/elf2uf2.py +++ b/tools/modm_tools/elf2uf2.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2022, Andrey Kunitsyn +# Copyright (c) 2023, Thomas Rush # # This file is part of the modm project. # @@ -25,32 +26,58 @@ (\* *only ARM Cortex-M targets*) """ -import os import struct from pathlib import Path - verbose = False +warned = False + +# uf2_config is a dictionary with the family name (which is basically just the +# start of the part name) as the key, and a two item tuple as the value. +# The first value of the tuple is the family id (a 32-bit number that was +# randomly assigned and agreed upon), and the second is a boolean indicating +# whether or not an FPU is available for this part. + +# IMPORTANT: The search for the family name using the target is straightforward, +# EXCEPT for three names which overlap: stm32f407vg, stm32f407, and stm32f4. +# Using 'startswith()' requires that they MUST be checked in that order. +# If target was 'stm32f407vgt6', for example, and the entry 'stm32f4' came +# first in the name list, then 'target.startswith(entry)' would be true and +# the search would end at that point, not reaching the correct entry of +# 'stm32f407vg'. Therefore, if any entries are added to this dictionary, the +# order of those three must NOT be changed. + +uf2_config = { + "rp2040": ( 0xe48bff56, False ), "samd21": ( 0x68ed2b88, False ), + # same5x shares its id with samd51 + "samd51": ( 0x55114460, True ), "same5": ( 0x55114460, True ), + "saml21": ( 0x1851780a, False ), "stm32f0": ( 0x647824b6, False ), + "stm32f1": ( 0x5ee21072, False ), "stm32f2": ( 0x5d1a0a2e, False ), + "stm32f3": ( 0x6b846188, True ), "stm32f407vg": ( 0x8fb060fe, True ), + "stm32f407": ( 0x6d0922fa, True ), "stm32f4": ( 0x57755a57, True ), + "stm32f7": ( 0x53b80f00, True ), "stm32g0": ( 0x300f5633, False ), + "stm32g4": ( 0x4c71240a, True ), "stm32h7": ( 0x6db66082, True ), + "stm32l0": ( 0x202e3a91, False ), "stm32l1": ( 0x1e1f432d, False ), + "stm32l4": ( 0x00ff6919, True ), "stm32l5": ( 0x04240bdf, True ), + "stm32wb": ( 0x70d16653, True ), "stm32wl": ( 0x21460ff0, True ) +} + +# UF2 constants +UF2_MAGIC_START0 = b'UF2\n' +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto 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), - } -} +UF2_PAGE_SIZE = (1 << 8) #struct uf2_block { # // 32 byte header -# uint32_t magic_start0; +# uint32_t magic_start0; encoded as a byte string '4s' for python # uint32_t magic_start1; # uint32_t flags; # uint32_t target_addr; @@ -58,114 +85,133 @@ # 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 is_phys_addr and range["type"] == "NO_CONTENTS" and size != 0: + err = "ELF has contents for uninitialized memory at {:08x}->{:08x}".format(addr,addr+size) + raise Exception(err) + return + + # No range was found. With some targets, external memory is added, or a contiguous region is split + # into 2 or more regions in the device file while the linkerscript provides a way to lump it into + # one. These added memory ranges (typically RAM) do not show up in the build system commands for + # this program, and strict adherence to the command-provided ranges can cause unnecessary failure. + # If 'size' is zero, the conversion continues without any message. When 'size' is non-zero, then if + # 'addr' is a paddr (physical address, meaning there is content to be loaded), an exception is + # raised and the program aborts; otherwise a warning is printed and the conversion continues. + # + # (As a side effect of continuing with missing ranges: when executing from the command line, the + # only range that MUST be provided is for the flash, with a type of 'CONTENTS'.) + + global warned + if size != 0: + err = "Memory segment {:08x}->{:08x} is outside of supplied address ranges.".format(addr, addr+size) + if is_phys_addr: + raise Exception(err) + if not warned: + # This message is printed once; the range message is printed for each offending segment. + print("Warning: ranges supplied don't match or don't include all those in ELF file.") + warned = True + print(err) + + +def read_and_check_elf32_ph_entries(buffer, ph_offset, ph_num, valid_ranges, pages, page_size): + for ph in range(ph_num): + type, offset, vaddr, paddr, filesz, memsz = struct.unpack_from(elf32_ph_entry,buffer,ph_offset+ph*elf32_ph_entry_size) + if type == PT_LOAD and memsz !=0: + check_address_range(valid_ranges, paddr, filesz, True) + check_address_range(valid_ranges, vaddr, memsz, False) 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]; + print(("{} segment {:08x}->{:08x} ({:08x}->{:08x})").format(memsz>filesz \ + and "Uninitialized" or "Mapped",paddr,paddr+filesz,vaddr,vaddr+memsz)) + if filesz != 0: + addr = paddr + remaining = filesz + file_offset = offset while remaining > 0: off = addr & (page_size - 1) chlen = min(remaining, page_size - off) @@ -174,62 +220,59 @@ def read_and_check_elf32_ph_entries(buffer, eh, valid_ranges, pages, page_size): if key in pages: fragments = pages[key] for fragment in fragments: - if (off < fragment["page_offset"] + fragment["bytes"]) != ((off + chlen) <= fragment["page_offset"]): + page_offset, byte_count = fragment[1:] + if (off < page_offset + byte_count) != ((off + chlen) <= page_offset): raise Exception("In memory segments overlap") - - fragments.append({"file_offset":file_offset,"page_offset":off,"bytes":chlen}) + fragments.append(tuple([file_offset, off, 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): +def realize_page(buffer, page): result = bytes(476) - for frag in fragments: - data = buffer[frag["file_offset"]:frag["file_offset"]+frag["bytes"]] - if len(data) != frag["bytes"]: + for frag in page: + file_offset, page_offset, byte_count = frag + data = buffer[file_offset:file_offset+byte_count] + if len(data) != byte_count: raise Exception("failed extract") - if frag["page_offset"] == 0: - result = data + result[frag["page_offset"]+frag["bytes"]:] + if page_offset == 0: + result = data + result[byte_count:] else: - result = result[:frag["page_offset"]] + data + result[frag["page_offset"]+frag["bytes"]:] + result = result[:page_offset] + data + result[page_offset+byte_count:] 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] +def convert_data(source_bytes, target, family, ranges): + family_id, has_fpu = uf2_config[family] + ph_offset, ph_num = read_header(source_bytes, has_fpu) if verbose: - print("Build for chip:{}".format(target)) + print("Build for chip {} in UF2 family {}".format(target, family)) pages = {} - read_and_check_elf32_ph_entries(source_bytes,eh,ranges,pages,config["PAGE_SIZE"]) + read_and_check_elf32_ph_entries(source_bytes, ph_offset, ph_num, ranges, pages, UF2_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(): + for target_addr, page in pages.items(): if verbose: - print("Page {} / {} {:08x}".format( page_num, num_blocks, target_addr)) + print("Page {} / {} {:08x}".format(page_num, num_blocks, target_addr)) - data = realize_page(source_bytes,pages) + data = realize_page(source_bytes,page) block = struct.pack(uf2_block, - config["MAGIC_START0"], - config["MAGIC_START1"], + UF2_MAGIC_START0, + UF2_MAGIC_START1, UF2_FLAG_FAMILY_ID_PRESENT, target_addr, - config["PAGE_SIZE"], + UF2_PAGE_SIZE, page_num, num_blocks, - config["FAMILY_ID"]) + data + struct.pack("= range["end"]: + err += "Supplied memory range {:08x}->{:08x} has length <= 0\n".format(range["start"],range["end"]) + if range["type"] == "CONTENTS": + no_content = False + if no_content: + err += "No ranges with type 'CONTENTS'\n" + if len(err) > 0: + raise Exception(err) + + def convert(source, output, target, ranges): + family = check_target(target) + check_valid_range(ranges) source_bytes = Path(source).read_bytes() - uf2 = convert_data(source_bytes,target, ranges) + uf2 = convert_data(source_bytes, target, family, ranges) Path(output).write_bytes(uf2) def parse_range(strval): if strval.startswith("0x"): - return int(strval[2:],16) + return int(strval,16) return int(strval) @@ -258,20 +326,17 @@ def parse_range(strval): dest="source", metavar="ELF", help="Input ELF binary") - parser.add_argument( - "-o", "--output", - dest="output", - required=True, - help="Destination UF2 image") parser.add_argument( "--verbose", - dest="verbose", action="store_true", help="Verbose output") + parser.add_argument( + "-o", "--output", + required=True, + help="Destination UF2 image") parser.add_argument( "--target", - dest="target", - default="rp2040", + required=True, help="Target chip") parser.add_argument( "--range", @@ -282,6 +347,7 @@ def parse_range(strval): args = parser.parse_args() verbose = args.verbose + target = args.target.lower() ranges = [] for r in args.ranges: start, end, t = r.split(":") @@ -291,4 +357,5 @@ def parse_range(strval): "type": t }) - convert(args.source, args.output, args.target, ranges) + + convert(args.source, args.output, target, ranges) From 8fc616ce86c8637d50eea5bb3e6624361eb0380b Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 5 Jun 2023 20:01:15 +0200 Subject: [PATCH 022/159] [driver] Compare calculated crc with MS5611 prom --- examples/nucleo_g474re/ms5611/main.cpp | 2 +- src/modm/driver/pressure/ms5611.hpp | 2 +- src/modm/driver/pressure/ms5611_impl.hpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/nucleo_g474re/ms5611/main.cpp b/examples/nucleo_g474re/ms5611/main.cpp index c1c39d693b..ff0b33d321 100644 --- a/examples/nucleo_g474re/ms5611/main.cpp +++ b/examples/nucleo_g474re/ms5611/main.cpp @@ -32,7 +32,7 @@ class BarometerThread : public modm::pt::Protothread { PT_BEGIN(); - while (PT_CALL(barometer.initialize()) == 0) + while (PT_CALL(barometer.initialize()) == false) { MODM_LOG_ERROR << "Ms5611 PROM CRC failed" << modm::endl; timeout.restart(std::chrono::milliseconds(1000)); diff --git a/src/modm/driver/pressure/ms5611.hpp b/src/modm/driver/pressure/ms5611.hpp index c41c76d34a..5fd61a36bf 100644 --- a/src/modm/driver/pressure/ms5611.hpp +++ b/src/modm/driver/pressure/ms5611.hpp @@ -90,7 +90,7 @@ class Ms5611 : public ms5611, public modm::SpiDevice, protected modm: /// Call this function before using the device to read the factory calibration /// @warning calls to this function resets the device - modm::ResumableResult + modm::ResumableResult initialize(); /// Do a readout sequence to convert and read temperature and then pressure from sensor diff --git a/src/modm/driver/pressure/ms5611_impl.hpp b/src/modm/driver/pressure/ms5611_impl.hpp index e71ed80191..a7e3412be7 100644 --- a/src/modm/driver/pressure/ms5611_impl.hpp +++ b/src/modm/driver/pressure/ms5611_impl.hpp @@ -28,7 +28,7 @@ Ms5611::Ms5611(DataBase &data) : data(data) // ----------------------------------------------------------------------------- template -modm::ResumableResult +modm::ResumableResult Ms5611::initialize() { RF_BEGIN(); @@ -67,7 +67,7 @@ Ms5611::initialize() data.prom.data[6] = modm::fromBigEndian(RF_CALL(readProm(6))); data.prom.data[7] = modm::fromBigEndian(RF_CALL(readProm(7))); - RF_END_RETURN(data.prom.calculateCrc()); + RF_END_RETURN(data.prom.calculateCrc() == (data.prom.data[7] & 0x000F)); } // ----------------------------------------------------------------------------- From 2557b1ac57027284a363e4329d20695208993c3f Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 1 Jul 2023 21:30:04 +0200 Subject: [PATCH 023/159] [timer] Remove deprecated setPeriod function --- src/modm/platform/timer/stm32/advanced.hpp.in | 9 --------- src/modm/platform/timer/stm32/basic.hpp.in | 9 --------- src/modm/platform/timer/stm32/general_purpose.hpp.in | 9 --------- 3 files changed, 27 deletions(-) diff --git a/src/modm/platform/timer/stm32/advanced.hpp.in b/src/modm/platform/timer/stm32/advanced.hpp.in index 29d0534357..e9d26dc31f 100644 --- a/src/modm/platform/timer/stm32/advanced.hpp.in +++ b/src/modm/platform/timer/stm32/advanced.hpp.in @@ -200,15 +200,6 @@ public: return overflow; } - // DEPRECATE: 2023q2 - template - [[deprecated("Pass microseconds as std::chrono::duration: setPeriod( {microseconds}us ) instead!")]] - static Value - setPeriod(uint32_t microseconds, bool autoApply = true) - { - return setPeriod(std::chrono::microseconds(microseconds), autoApply); - } - static inline void generateEvent(Event ev) { diff --git a/src/modm/platform/timer/stm32/basic.hpp.in b/src/modm/platform/timer/stm32/basic.hpp.in index 6e25038a55..e7c246fb42 100644 --- a/src/modm/platform/timer/stm32/basic.hpp.in +++ b/src/modm/platform/timer/stm32/basic.hpp.in @@ -141,15 +141,6 @@ public: return overflow; } - // DEPRECATE: 2023q2 - template - [[deprecated("Pass microseconds as std::chrono::duration: setPeriod( {microseconds}us ) instead!")]] - static Value - setPeriod(uint32_t microseconds, bool autoApply = true) - { - return setPeriod(std::chrono::microseconds(microseconds), autoApply); - } - static inline void generateEvent(Event ev) { diff --git a/src/modm/platform/timer/stm32/general_purpose.hpp.in b/src/modm/platform/timer/stm32/general_purpose.hpp.in index ad6ccbfeb4..cf79e2142d 100644 --- a/src/modm/platform/timer/stm32/general_purpose.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.hpp.in @@ -224,15 +224,6 @@ public: return overflow; } - // DEPRECATE: 2023q2 - template - [[deprecated("Pass microseconds as std::chrono::duration: setPeriod( {microseconds}us ) instead!")]] - static Value - setPeriod(uint32_t microseconds, bool autoApply = true) - { - return setPeriod(std::chrono::microseconds(microseconds), autoApply); - } - /* Returns the frequency of the timer */ template static uint32_t From fe2b6c3efe1a84edd35c813ff055f878d4912238 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 25 Jun 2023 17:50:10 +0200 Subject: [PATCH 024/159] [ext] Update submodules to latest version --- ext/adamgreen/crashcatcher | 2 +- ext/gcc/libstdc++ | 2 +- ext/lvgl/lvgl | 2 +- ext/rp/pico-sdk | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/adamgreen/crashcatcher b/ext/adamgreen/crashcatcher index 171da5e4bd..c8e508b6d5 160000 --- a/ext/adamgreen/crashcatcher +++ b/ext/adamgreen/crashcatcher @@ -1 +1 @@ -Subproject commit 171da5e4bdd848e7bbc9654b170dab633651e3ad +Subproject commit c8e508b6d5e7255bb78d334d7442dadec068b059 diff --git a/ext/gcc/libstdc++ b/ext/gcc/libstdc++ index 13363df680..dd2953c4c5 160000 --- a/ext/gcc/libstdc++ +++ b/ext/gcc/libstdc++ @@ -1 +1 @@ -Subproject commit 13363df68028664c262151e42c1b96c54e1744e5 +Subproject commit dd2953c4c565262ce2be2147bb1cbb8188ffa2df diff --git a/ext/lvgl/lvgl b/ext/lvgl/lvgl index 8f5eadbd6e..b51785ea99 160000 --- a/ext/lvgl/lvgl +++ b/ext/lvgl/lvgl @@ -1 +1 @@ -Subproject commit 8f5eadbd6e6e85c90889b3d7a7058176175d90c4 +Subproject commit b51785ea997ac8e1d0f0b3b4e1b247349cee7217 diff --git a/ext/rp/pico-sdk b/ext/rp/pico-sdk index 146180924e..fa907dafad 160000 --- a/ext/rp/pico-sdk +++ b/ext/rp/pico-sdk @@ -1 +1 @@ -Subproject commit 146180924eade25dadf07e80907b8706ddde068c +Subproject commit fa907dafad7da1427e2e1c3b10d86b35276f5fab From 68f9a8261f1ec37b29b480fe5e382b98f0f3b41b Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 1 Jul 2023 21:29:40 +0200 Subject: [PATCH 025/159] [release] Update changelog for 2023q3 release --- CHANGELOG.md | 192 +++++++++++++++++++++++++++++++++++++++++ docs/release/2023q2.md | 155 +++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 docs/release/2023q2.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ef7038210e..bc703a9331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,163 @@ pay attention to. Medium impact changes are also worth looking at. +## 2023-07-01: 2023q2 release + +This release covers everything from 2023-04-05 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Breaking changes: + +- GCC12 requirement for C++23. +- `-fsingle-precision-constant` default compile flag has been removed. +- Removed deprecated `Timer::setPeriod(uint32_t)` in favor of`std::chrono` units. + +Features: + +- SAMx7x DAC, ADC, DMA, and CAN drivers. +- Enabled C++23 and C23. +- STM32 IWDG driver. +- Fibers are now backward compatible with protothreads and resumable functions. +- Support for STM32G0B/C devices with shared interrupts. + +Integrated Projects: + +- LVGL upgraded to v8.3.7. +- Pico-SDK upgraded to v1.5.1. +- STM32F1 headers upgraded to v4.3.4. +- STM32F2 headers upgraded to v2.2.6. +- STM32L1 headers upgraded to v2.3.3. +- CMSIS-DSP upgraded to v1.14.4. +- SAMx7x upgraded to v3.0. +- TinyUSB upgraded to v0.15.0. + +Fixes: + +- Moving average type selection. +- SysTick clock access from both cores on RP2040. +- FDCAN driver on STM32 tx message queue. +- STM32 I2C NACK flag is acknowledged for instances >1. +- Fix arithmetic overflow in `Timer::setPeriod` on STM32. +- Validate calculated CRC on MS5611 driver. + +New development boards: + +- Adafruit Feather-M4 as [`modm:feather-m4`][]. + +New device drivers: + +- MAX31865 temperature sensor as [`modm:driver:max31865`][]. +- Internal cycle counter as [`modm:driver:cycle_counter`][]. +- MCP3008 ADC driver as [`modm:driver:mcp3008`][]. + +Known bugs: + +- STM32F7: D-Cache not enabled by default. See [#485][]. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See [#285][]. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See [#310][]. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See [#591][]. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Christopher Durand ([@chris-durand][]) +- Daniel Waldhäusl 🎉 +- Henrik Hose ([@hshose][]) +- Niklas Hauser ([@salkinium][]) +- Raphael Lehmann ([@rleh][]) +- Rasmus Kleist ([@rasmuskleist][]) +- Sascha Schade ([@strongly-typed][]) +- Sergey Pluzhnikov ([@ser-plu][]) +- Thomas Rush ([@tarush53][]) +- Victor Costa ([@victorandrehc][]) +- Vivien Henry ([@lukh][]) + +PR [#1044][] -> [2023q2][]. + +
+Detailed changelog + +#### 2023-06-20: Extend support for ELF2UF2 tool to STM32 and SAM + +PR [#1038][] -> [b05df3e][]. +Tested in hardware by [@tarush53][]. + +#### 2023-06-09: Remove `-fsingle-precision-constant` compile flag + +PR [#1037][] -> [2384756][] with medium-impact in floating point variables. +Tested in hardware by [@salkinium][]. + +#### 2023-06-08: Add Adafruit Feather-M4 board support + +PR [#1032][] -> [81b86be][]. +Tested in hardware by [@tarush53][]. + +#### 2023-06-07: Add support for STM32G0B/C devices + +PR [#1036][] -> [768d749][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-06-01: Add MCP3008 ADC driver + +PR [#1028][] -> [eda224e][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-05-19: Add complementary channels to TIM driver on STM32 + +PR [#1018][] -> [45ae68a][]. +Tested in hardware by [@ser-plu][]. + +#### 2023-05-15: Implement Protothreads/Resumables using Fibers + +PR [#1001][] -> [45ae68a][]. +Tested in hardware by [@salkinium][]. + +#### 2023-05-13: Fix FDCAN transmission queue on STM32 + +PR [#1017][] -> [9d33843][]. +Tested in hardware by [@ser-plu][], [@chris-durand][], and [@rleh][]. + +#### 2023-05-09: Add MCAN driver for SAMx7x + +PR [#955][] -> [bfafcd3][]. +Tested in hardware by [@rleh][]. + +#### 2023-05-05: Add IWDG driver for STM32 + +PR [#1009][] -> [d772940][]. +Tested in hardware by Daniel Waldhäusl. + +#### 2023-05-03: Fix RP2040 multicore access to modm::Clock + +PR [#1010][] -> [389a9c3][]. +Tested in hardware by [@salkinium][]. + +#### 2023-05-02: Add MAX31865 temperature sensor + +PR [#993][] -> [65bbccf][]. +Tested in hardware by [@hshose][]. + +#### 2023-04-19: Add ADC driver for SAMx7x + +PR [#998][] -> [c7c4c57][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-04-12: Add DAC and DMA driver for SAMx7x + +PR [#987][] -> [94580b4][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-04-08: Fix STM32 SPI configuration while running + +PR [#994][] -> [972b74b][]. +Tested in hardware by [@chris-durand][]. + +
+ + ## 2023-04-06: 2023q1 release This release covers everything from 2023-01-01 and has been tested with avr-gcc @@ -2502,6 +2659,7 @@ Please note that contributions from xpcc were continuously ported to modm. [2022q3]: https://github.com/modm-io/modm/releases/tag/2022q3 [2022q4]: https://github.com/modm-io/modm/releases/tag/2022q4 [2023q1]: https://github.com/modm-io/modm/releases/tag/2023q1 +[2023q2]: https://github.com/modm-io/modm/releases/tag/2023q2 [@19joho66]: https://github.com/19joho66 [@ASMfreaK]: https://github.com/ASMfreaK @@ -2601,6 +2759,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:at24mac402`]: https://modm.io/reference/module/modm-driver-at24mac402 [`modm:driver:bno055`]: https://modm.io/reference/module/modm-driver-bno055 [`modm:driver:cat24aa`]: https://modm.io/reference/module/modm-driver-cat24aa +[`modm:driver:cycle_counter`]: https://modm.io/reference/module/modm-driver-cycle_counter [`modm:driver:encoder.output`]: https://modm.io/reference/module/modm-driver-encoder-output [`modm:driver:encoder_input.bitbang`]: https://modm.io/reference/module/modm-driver-encoder_input-bitbang [`modm:driver:encoder_input`]: https://modm.io/reference/module/modm-driver-encoder_input @@ -2612,7 +2771,9 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:lp503x`]: https://modm.io/reference/module/modm-driver-lp503x [`modm:driver:lsm6ds33`]: https://modm.io/reference/module/modm-driver-lsm6ds33 [`modm:driver:lsm6dso`]: https://modm.io/reference/module/modm-driver-lsm6dso +[`modm:driver:max31865`]: https://modm.io/reference/module/modm-driver-max31865 [`modm:driver:max7219`]: https://modm.io/reference/module/modm-driver-max7219 +[`modm:driver:mcp3008`]: https://modm.io/reference/module/modm-driver-mcp3008 [`modm:driver:mcp7941x`]: https://modm.io/reference/module/modm-driver-mcp7941x [`modm:driver:mcp990x`]: https://modm.io/reference/module/modm-driver-mcp990x [`modm:driver:mmc5603`]: https://modm.io/reference/module/modm-driver-mmc5603 @@ -2631,9 +2792,21 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:tmp12x`]: https://modm.io/reference/module/modm-driver-tmp12x [`modm:driver:touch2046`]: https://modm.io/reference/module/modm-driver-touch2046 [`modm:driver:ws2812`]: https://modm.io/reference/module/modm-driver-ws2812 +[`modm:feather-m4`]: https://modm.io/reference/module/modm-feather-m4 [`modm:nucleo-u575zi-q`]: https://modm.io/reference/module/modm-nucleo-u575zi-q +[#1001]: https://github.com/modm-io/modm/pull/1001 +[#1009]: https://github.com/modm-io/modm/pull/1009 +[#1010]: https://github.com/modm-io/modm/pull/1010 +[#1017]: https://github.com/modm-io/modm/pull/1017 +[#1018]: https://github.com/modm-io/modm/pull/1018 +[#1028]: https://github.com/modm-io/modm/pull/1028 [#102]: https://github.com/modm-io/modm/pull/102 +[#1032]: https://github.com/modm-io/modm/pull/1032 +[#1036]: https://github.com/modm-io/modm/pull/1036 +[#1037]: https://github.com/modm-io/modm/pull/1037 +[#1038]: https://github.com/modm-io/modm/pull/1038 +[#1044]: https://github.com/modm-io/modm/pull/1044 [#118]: https://github.com/modm-io/modm/pull/118 [#122]: https://github.com/modm-io/modm/pull/122 [#132]: https://github.com/modm-io/modm/pull/132 @@ -2822,6 +2995,7 @@ Please note that contributions from xpcc were continuously ported to modm. [#951]: https://github.com/modm-io/modm/pull/951 [#952]: https://github.com/modm-io/modm/pull/952 [#954]: https://github.com/modm-io/modm/pull/954 +[#955]: https://github.com/modm-io/modm/pull/955 [#956]: https://github.com/modm-io/modm/pull/956 [#957]: https://github.com/modm-io/modm/pull/957 [#960]: https://github.com/modm-io/modm/pull/960 @@ -2836,7 +3010,11 @@ Please note that contributions from xpcc were continuously ported to modm. [#981]: https://github.com/modm-io/modm/pull/981 [#982]: https://github.com/modm-io/modm/pull/982 [#986]: https://github.com/modm-io/modm/pull/986 +[#987]: https://github.com/modm-io/modm/pull/987 +[#993]: https://github.com/modm-io/modm/pull/993 +[#994]: https://github.com/modm-io/modm/pull/994 [#995]: https://github.com/modm-io/modm/pull/995 +[#998]: https://github.com/modm-io/modm/pull/998 [00471ca]: https://github.com/modm-io/modm/commit/00471ca [0217a19]: https://github.com/modm-io/modm/commit/0217a19 @@ -2872,6 +3050,7 @@ Please note that contributions from xpcc were continuously ported to modm. [21ba120]: https://github.com/modm-io/modm/commit/21ba120 [2273bae]: https://github.com/modm-io/modm/commit/2273bae [22867e0]: https://github.com/modm-io/modm/commit/22867e0 +[2384756]: https://github.com/modm-io/modm/commit/2384756 [23ec952]: https://github.com/modm-io/modm/commit/23ec952 [241b0d1]: https://github.com/modm-io/modm/commit/241b0d1 [276f5b3]: https://github.com/modm-io/modm/commit/276f5b3 @@ -2883,6 +3062,7 @@ Please note that contributions from xpcc were continuously ported to modm. [3072005]: https://github.com/modm-io/modm/commit/3072005 [30e24e6]: https://github.com/modm-io/modm/commit/30e24e6 [387a625]: https://github.com/modm-io/modm/commit/387a625 +[389a9c3]: https://github.com/modm-io/modm/commit/389a9c3 [3936a28]: https://github.com/modm-io/modm/commit/3936a28 [399a533]: https://github.com/modm-io/modm/commit/399a533 [3ba71c9]: https://github.com/modm-io/modm/commit/3ba71c9 @@ -2891,6 +3071,7 @@ Please note that contributions from xpcc were continuously ported to modm. [3f3ff3d]: https://github.com/modm-io/modm/commit/3f3ff3d [416ced6]: https://github.com/modm-io/modm/commit/416ced6 [43f32e6]: https://github.com/modm-io/modm/commit/43f32e6 +[45ae68a]: https://github.com/modm-io/modm/commit/45ae68a [47adfd6]: https://github.com/modm-io/modm/commit/47adfd6 [4885c53]: https://github.com/modm-io/modm/commit/4885c53 [48d73dc]: https://github.com/modm-io/modm/commit/48d73dc @@ -2917,6 +3098,7 @@ Please note that contributions from xpcc were continuously ported to modm. [62b63f5]: https://github.com/modm-io/modm/commit/62b63f5 [62ccc26]: https://github.com/modm-io/modm/commit/62ccc26 [64d177a]: https://github.com/modm-io/modm/commit/64d177a +[65bbccf]: https://github.com/modm-io/modm/commit/65bbccf [66c0868]: https://github.com/modm-io/modm/commit/66c0868 [6b4d656]: https://github.com/modm-io/modm/commit/6b4d656 [6b5b4ce]: https://github.com/modm-io/modm/commit/6b5b4ce @@ -2926,6 +3108,7 @@ Please note that contributions from xpcc were continuously ported to modm. [72d5ae9]: https://github.com/modm-io/modm/commit/72d5ae9 [7330500]: https://github.com/modm-io/modm/commit/7330500 [740fd51]: https://github.com/modm-io/modm/commit/740fd51 +[768d749]: https://github.com/modm-io/modm/commit/768d749 [77ae899]: https://github.com/modm-io/modm/commit/77ae899 [78d18f6]: https://github.com/modm-io/modm/commit/78d18f6 [7b5827f]: https://github.com/modm-io/modm/commit/7b5827f @@ -2936,6 +3119,7 @@ Please note that contributions from xpcc were continuously ported to modm. [80a9c66]: https://github.com/modm-io/modm/commit/80a9c66 [80ed738]: https://github.com/modm-io/modm/commit/80ed738 [8179e6b]: https://github.com/modm-io/modm/commit/8179e6b +[81b86be]: https://github.com/modm-io/modm/commit/81b86be [821677b]: https://github.com/modm-io/modm/commit/821677b [8230fef]: https://github.com/modm-io/modm/commit/8230fef [82bc4a9]: https://github.com/modm-io/modm/commit/82bc4a9 @@ -2955,11 +3139,14 @@ Please note that contributions from xpcc were continuously ported to modm. [923f9c1]: https://github.com/modm-io/modm/commit/923f9c1 [9381fd0]: https://github.com/modm-io/modm/commit/9381fd0 [93bba13]: https://github.com/modm-io/modm/commit/93bba13 +[94580b4]: https://github.com/modm-io/modm/commit/94580b4 [95713ee]: https://github.com/modm-io/modm/commit/95713ee +[972b74b]: https://github.com/modm-io/modm/commit/972b74b [98a2483]: https://github.com/modm-io/modm/commit/98a2483 [98b1337]: https://github.com/modm-io/modm/commit/98b1337 [9b6aeee]: https://github.com/modm-io/modm/commit/9b6aeee [9cbea26]: https://github.com/modm-io/modm/commit/9cbea26 +[9d33843]: https://github.com/modm-io/modm/commit/9d33843 [9d8bbfa]: https://github.com/modm-io/modm/commit/9d8bbfa [9e285db]: https://github.com/modm-io/modm/commit/9e285db [9e50a16]: https://github.com/modm-io/modm/commit/9e50a16 @@ -2977,6 +3164,7 @@ Please note that contributions from xpcc were continuously ported to modm. [afbd533]: https://github.com/modm-io/modm/commit/afbd533 [afdb5ba]: https://github.com/modm-io/modm/commit/afdb5ba [b010775]: https://github.com/modm-io/modm/commit/b010775 +[b05df3e]: https://github.com/modm-io/modm/commit/b05df3e [b153186]: https://github.com/modm-io/modm/commit/b153186 [b18385c]: https://github.com/modm-io/modm/commit/b18385c [b1e5588]: https://github.com/modm-io/modm/commit/b1e5588 @@ -2986,6 +3174,7 @@ Please note that contributions from xpcc were continuously ported to modm. [b78acd5]: https://github.com/modm-io/modm/commit/b78acd5 [b8648be]: https://github.com/modm-io/modm/commit/b8648be [ba61a34]: https://github.com/modm-io/modm/commit/ba61a34 +[bfafcd3]: https://github.com/modm-io/modm/commit/bfafcd3 [c0a8c51]: https://github.com/modm-io/modm/commit/c0a8c51 [c148bf8]: https://github.com/modm-io/modm/commit/c148bf8 [c347f00]: https://github.com/modm-io/modm/commit/c347f00 @@ -2993,6 +3182,7 @@ Please note that contributions from xpcc were continuously ported to modm. [c63a536]: https://github.com/modm-io/modm/commit/c63a536 [c7b35ca]: https://github.com/modm-io/modm/commit/c7b35ca [c7bd876]: https://github.com/modm-io/modm/commit/c7bd876 +[c7c4c57]: https://github.com/modm-io/modm/commit/c7c4c57 [c868f59]: https://github.com/modm-io/modm/commit/c868f59 [c93dd2c]: https://github.com/modm-io/modm/commit/c93dd2c [c949daf]: https://github.com/modm-io/modm/commit/c949daf @@ -3006,6 +3196,7 @@ Please note that contributions from xpcc were continuously ported to modm. [d2d38a0]: https://github.com/modm-io/modm/commit/d2d38a0 [d3496a3]: https://github.com/modm-io/modm/commit/d3496a3 [d46c09d]: https://github.com/modm-io/modm/commit/d46c09d +[d772940]: https://github.com/modm-io/modm/commit/d772940 [d8be0a2]: https://github.com/modm-io/modm/commit/d8be0a2 [d982a85]: https://github.com/modm-io/modm/commit/d982a85 [dab6c79]: https://github.com/modm-io/modm/commit/dab6c79 @@ -3020,6 +3211,7 @@ Please note that contributions from xpcc were continuously ported to modm. [e4b1a4a]: https://github.com/modm-io/modm/commit/e4b1a4a [eb2748e]: https://github.com/modm-io/modm/commit/eb2748e [eba68a4]: https://github.com/modm-io/modm/commit/eba68a4 +[eda224e]: https://github.com/modm-io/modm/commit/eda224e [f4c7492]: https://github.com/modm-io/modm/commit/f4c7492 [f4d5d6c]: https://github.com/modm-io/modm/commit/f4d5d6c [f5cdf6a]: https://github.com/modm-io/modm/commit/f5cdf6a diff --git a/docs/release/2023q2.md b/docs/release/2023q2.md new file mode 100644 index 0000000000..e595ed0921 --- /dev/null +++ b/docs/release/2023q2.md @@ -0,0 +1,155 @@ +## 2023-07-01: 2023q2 release + +This release covers everything from 2023-04-05 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Breaking changes: + +- GCC12 requirement for C++23. +- `-fsingle-precision-constant` default compile flag has been removed. +- Removed deprecated `Timer::setPeriod(uint32_t)` in favor of`std::chrono` units. + +Features: + +- SAMx7x DAC, ADC, DMA, and CAN drivers. +- Enabled C++23 and C23. +- STM32 IWDG driver. +- Fibers are now backward compatible with protothreads and resumable functions. +- Support for STM32G0B/C devices with shared interrupts. + +Integrated Projects: + +- LVGL upgraded to v8.3.7. +- Pico-SDK upgraded to v1.5.1. +- STM32F1 headers upgraded to v4.3.4. +- STM32F2 headers upgraded to v2.2.6. +- STM32L1 headers upgraded to v2.3.3. +- CMSIS-DSP upgraded to v1.14.4. +- SAMx7x upgraded to v3.0. +- TinyUSB upgraded to v0.15.0. + +Fixes: + +- Moving average type selection. +- SysTick clock access from both cores on RP2040. +- FDCAN driver on STM32 tx message queue. +- STM32 I2C NACK flag is acknowledged for instances >1. +- Fix arithmetic overflow in `Timer::setPeriod` on STM32. +- Validate calculated CRC on MS5611 driver. + +New development boards: + +- Adafruit Feather-M4 as `modm:feather-m4`. + +New device drivers: + +- MAX31865 temperature sensor as `modm:driver:max31865`. +- Internal cycle counter as `modm:driver:cycle_counter`. +- MCP3008 ADC driver as `modm:driver:mcp3008`. + +Known bugs: + +- STM32F7: D-Cache not enabled by default. See #485. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See #285. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See #310. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See #591. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Christopher Durand (@chris-durand) +- Daniel Waldhäusl 🎉 +- Henrik Hose (@hshose) +- Niklas Hauser (@salkinium) +- Raphael Lehmann (@rleh) +- Rasmus Kleist (@rasmuskleist) +- Sascha Schade (@strongly-typed) +- Sergey Pluzhnikov (@ser-plu) +- Thomas Rush (@tarush53) +- Victor Costa (@victorandrehc) +- Vivien Henry (@lukh) + +PR #1044 -> 2023q2. + +
+Detailed changelog + +#### 2023-06-20: Extend support for ELF2UF2 tool to STM32 and SAM + +PR #1038 -> b05df3e. +Tested in hardware by @tarush53. + +#### 2023-06-09: Remove `-fsingle-precision-constant` compile flag + +PR #1037 -> 2384756 with medium-impact in floating point variables. +Tested in hardware by @salkinium. + +#### 2023-06-08: Add Adafruit Feather-M4 board support + +PR #1032 -> 81b86be. +Tested in hardware by @tarush53. + +#### 2023-06-07: Add support for STM32G0B/C devices + +PR #1036 -> 768d749. +Tested in hardware by @chris-durand. + +#### 2023-06-01: Add MCP3008 ADC driver + +PR #1028 -> eda224e. +Tested in hardware by @chris-durand. + +#### 2023-05-19: Add complementary channels to TIM driver on STM32 + +PR #1018 -> 45ae68a. +Tested in hardware by @ser-plu. + +#### 2023-05-15: Implement Protothreads/Resumables using Fibers + +PR #1001 -> 45ae68a. +Tested in hardware by @salkinium. + +#### 2023-05-13: Fix FDCAN transmission queue on STM32 + +PR #1017 -> 9d33843. +Tested in hardware by @ser-plu, @chris-durand, and @rleh. + +#### 2023-05-09: Add MCAN driver for SAMx7x + +PR #955 -> bfafcd3. +Tested in hardware by @rleh. + +#### 2023-05-05: Add IWDG driver for STM32 + +PR #1009 -> d772940. +Tested in hardware by Daniel Waldhäusl. + +#### 2023-05-03: Fix RP2040 multicore access to modm::Clock + +PR #1010 -> 389a9c3. +Tested in hardware by @salkinium. + +#### 2023-05-02: Add MAX31865 temperature sensor + +PR #993 -> 65bbccf. +Tested in hardware by @hshose. + +#### 2023-04-19: Add ADC driver for SAMx7x + +PR #998 -> c7c4c57. +Tested in hardware by @chris-durand. + +#### 2023-04-12: Add DAC and DMA driver for SAMx7x + +PR #987 -> 94580b4. +Tested in hardware by @chris-durand. + +#### 2023-04-08: Fix STM32 SPI configuration while running + +PR #994 -> 972b74b. +Tested in hardware by @chris-durand. + +
From 027811f1d0b011236cb5d3c3d2a692032c821728 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 10 Jul 2023 17:03:49 +0200 Subject: [PATCH 026/159] [stm32] Place .data section into D1_SRAM instead of DTCM for H7 The data sections were previously placed into the first memory of the contiguous section with the lowest address. The selection is changed such that the first memory of the largest contiguous ram section is used. For all supported Cortex-M devices except STM32H7 the resulting linker script will be identical. On STM32H7 .data is currently placed into the DTCM which is not accessible by peripheral DMA transfers. With the new method the D1 AXI SRAM is used for .data while .fastdata remains in the DTCM. --- src/modm/platform/core/cortex/linker.macros | 6 ++++-- src/modm/platform/core/cortex/ram.ld.in | 10 ++++----- src/modm/platform/core/stm32/dccm.ld.in | 10 ++++----- src/modm/platform/core/stm32/iccm.ld.in | 8 +++---- src/modm/platform/core/stm32/idtcm.ld.in | 20 +++++++++++++----- src/modm/platform/core/stm32/module.md | 21 ++++++++++--------- .../core/stm32/ram_remap_vector_table.ld.in | 10 ++++----- 7 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/modm/platform/core/cortex/linker.macros b/src/modm/platform/core/cortex/linker.macros index d8ecf7ad8e..2a719eb890 100644 --- a/src/modm/platform/core/cortex/linker.macros +++ b/src/modm/platform/core/cortex/linker.macros @@ -5,6 +5,7 @@ * Copyright (c) 2012, 2015-2022, Niklas Hauser * Copyright (c) 2013, Sascha Schade * Copyright (c) 2013, 2015, Kevin Läufer + * Copyright (c) 2023, Christopher Durand * * This file is part of the modm project. * @@ -172,15 +173,16 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }}; %% macro all_heap_sections(table_copy, table_zero, table_heap, props={}) + %% set ram_first_index = cont_ram["contains"][0]["index"] %% for cont_region in cont_ram_regions %% for region in cont_region.contains /* Sections in {{ region.name|upper }} */ - %% if region.index + %% if region.index != ram_first_index {{ section_load(cont_region.cont_name|upper + " AT >FLASH", table_copy, sections=["data_"+region.name]) }} %% do table_zero.append("bss_"+region.name) %% endif {{ section_heap(region.name|upper, "heap_"+region.name, cont_region.cont_name|upper, - sections=(["bss_"+region.name] if region.index else []) + ["noinit_"+region.name]) }} + sections=(["bss_"+region.name] if region.index != ram_first_index else []) + ["noinit_"+region.name]) }} %% for name, value in props.items() if name in region.name %% do table_heap.insert(region.index, {"name": "heap_"+region.name, "prop": value}) %% else diff --git a/src/modm/platform/core/cortex/ram.ld.in b/src/modm/platform/core/cortex/ram.ld.in index 53dc0eac69..9913453198 100644 --- a/src/modm/platform/core/cortex/ram.ld.in +++ b/src/modm/platform/core/cortex/ram.ld.in @@ -20,15 +20,15 @@ SECTIONS {{ linker.section_rom("FLASH") }} -{{ linker.section_stack(cont_ram_regions[0].cont_name|upper, "__stack_offset" if vector_table_location == "ram" else None) }} +{{ linker.section_stack(cont_ram.cont_name|upper, "__stack_offset" if vector_table_location == "ram" else None) }} %% if vector_table_location == "ram" -{{ linker.section_vector_ram(cont_ram_regions[0].cont_name|upper, table_copy) }} +{{ linker.section_vector_ram(cont_ram.cont_name|upper, 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], +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["fastdata", "fastcode", "data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], sections_noinit=["faststack"]) }} {{ linker.all_heap_sections(table_copy, table_zero, table_heap) }} diff --git a/src/modm/platform/core/stm32/dccm.ld.in b/src/modm/platform/core/stm32/dccm.ld.in index d6beef179e..e478212390 100644 --- a/src/modm/platform/core/stm32/dccm.ld.in +++ b/src/modm/platform/core/stm32/dccm.ld.in @@ -20,15 +20,15 @@ SECTIONS {{ linker.section_rom("FLASH") }} -{{ linker.section_stack(cont_ram_regions[0].cont_name|upper, "__stack_offset" if vector_table_location == "ram" else None) }} +{{ linker.section_stack(cont_ram.cont_name|upper, "__stack_offset" if vector_table_location == "ram" else None) }} %% if vector_table_location == "ram" -{{ linker.section_vector_ram(cont_ram_regions[0].cont_name|upper, table_copy) }} +{{ linker.section_vector_ram(cont_ram.cont_name|upper, table_copy) }} %% endif -{{ linker.section_ram(cont_ram_regions[0].cont_name|upper, "FLASH", table_copy, table_zero, - sections_data=["fastcode", "data_" + cont_ram_regions[0].contains[0].name], - sections_bss=["bss_" + cont_ram_regions[0].contains[0].name], +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["fastcode", "data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], sections_noinit=["faststack"]) }} {{ linker.all_heap_sections(table_copy, table_zero, table_heap) }} diff --git a/src/modm/platform/core/stm32/iccm.ld.in b/src/modm/platform/core/stm32/iccm.ld.in index 5eefaf4236..c3eaf1b2aa 100644 --- a/src/modm/platform/core/stm32/iccm.ld.in +++ b/src/modm/platform/core/stm32/iccm.ld.in @@ -15,11 +15,11 @@ SECTIONS {{ linker.section_rom("FLASH") }} -{{ linker.section_stack(cont_ram_regions[0].cont_name|upper) }} +{{ linker.section_stack(cont_ram.cont_name|upper) }} -{{ linker.section_ram(cont_ram_regions[0].cont_name|upper, "FLASH", table_copy, table_zero, - sections_data=["data_" + cont_ram_regions[0].contains[0].name], - sections_bss=["bss_" + cont_ram_regions[0].contains[0].name], +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], sections_noinit=["faststack"]) }} {{ linker.all_heap_sections(table_copy, table_zero, table_heap) }} diff --git a/src/modm/platform/core/stm32/idtcm.ld.in b/src/modm/platform/core/stm32/idtcm.ld.in index ec9d090212..8a8a9152c0 100644 --- a/src/modm/platform/core/stm32/idtcm.ld.in +++ b/src/modm/platform/core/stm32/idtcm.ld.in @@ -26,15 +26,25 @@ SECTIONS %% do table_heap.append({"name": "heap_itcm", "prop": "0x2005"}) %% if cont_ram_regions[0].cont_name == "cont_dtcm" -{{ linker.section_stack("CONT_DTCM") }} +%% set dtcm_section = "CONT_DTCM" %% else -{{ linker.section_stack("DTCM") }} +%% set dtcm_section = "DTCM" %% endif -{{ linker.section_ram(cont_ram_regions[0].cont_name|upper, "FLASH", table_copy, table_zero, - sections_data=["fastdata", "data_" + cont_ram_regions[0].contains[0].name], - sections_bss=["bss_" + cont_ram_regions[0].contains[0].name], +{{ linker.section_stack(dtcm_section) }} + +%% if "dtcm" in cont_ram.cont_name +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["fastdata", "data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], + sections_noinit=["faststack"]) }} +%% else +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], sections_noinit=["faststack"]) }} +{{ linker.section_load(dtcm_section + " AT >FLASH", table_copy, sections=["fastdata"]) }} +%% endif {{ linker.all_heap_sections(table_copy, table_zero, table_heap, props={"dtcm": "0x2023" if target.family == "h7" else "0x202b"}) }} %% if with_crashcatcher diff --git a/src/modm/platform/core/stm32/module.md b/src/modm/platform/core/stm32/module.md index 54d63a1b6c..7f8cf67ffe 100644 --- a/src/modm/platform/core/stm32/module.md +++ b/src/modm/platform/core/stm32/module.md @@ -255,8 +255,7 @@ not DMA-able. ### Tightly-Coupled RAM (TCM) The STM32F7 devices include an Instruction TCM (ITCM) and a data TCM (DTCM). -Note that the TCMs are DMA-able, but only the MDMA. Note that DTCM but will -overflow into the SRAM1/2 sections. +Note that the DTCM section will overflow into the SRAM1/2 sections. ``` ┌────────────────────────┐◄ __backup_end @@ -332,7 +331,8 @@ overflow into the SRAM1/2 sections. The STM32H7 memory map is more complex with multiple SRAM regions in seperate power domains D1, D2 and D3. Note that the main `.data` and `.bss` sections are -placed into the 128kB DTCM, but cannot overflow into D1_SRAM section. +placed into the D1_SRAM section because the TCMs can't be accessed by +peripheral DMA transfers, but only the MDMA. ``` ┌────────────────────────┐◄ __backup_end @@ -379,18 +379,19 @@ placed into the 128kB DTCM, but cannot overflow into D1_SRAM section. 0x2404 0000 ├────────────────────────┤◄ __d1_sram1_end, __d1_sram2_start │ +HEAP_D1_SRAM1 │ │ .noinit_d1_sram1 │ + │ .noinit │ + │ .faststack │ │ .bss_d1_sram1 │ - D1_SRAM1 │ .data_d1_sram1 │ + │ .bss │ + │ .data_d1_sram1 │ + D1_SRAM1 │ .data │ 0x2400 0000 └────────────────────────┘◄ __d1_sram1_start ┌────────────────────────┐◄ __dtcm_end │ +HEAP_DTCM │ - │ .noinit_dtcm │ - │ .noinit │ - │ .faststack │ - D-Code │ .bss_dtcm │ - only │ .data_dtcm │ - access │ .data │ + D-Code │ .noinit_dtcm │ + only │ .bss_dtcm │ + access │ .data_dtcm │ │ .fastdata │ DTCM │ +MAIN_STACK_SIZE │◄ __main_stack_top 0x2000 0000 └────────────────────────┘◄ __dtcm_start diff --git a/src/modm/platform/core/stm32/ram_remap_vector_table.ld.in b/src/modm/platform/core/stm32/ram_remap_vector_table.ld.in index af711cb0d8..2c55aed849 100644 --- a/src/modm/platform/core/stm32/ram_remap_vector_table.ld.in +++ b/src/modm/platform/core/stm32/ram_remap_vector_table.ld.in @@ -14,13 +14,13 @@ SECTIONS {{ linker.section_rom("FLASH") }} -{{ linker.section_vector_ram(cont_ram_regions[0].cont_name|upper, table_copy) }} +{{ linker.section_vector_ram(cont_ram.cont_name|upper, table_copy) }} -{{ linker.section_stack(cont_ram_regions[0].cont_name|upper) }} +{{ linker.section_stack(cont_ram.cont_name|upper) }} -{{ 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], +{{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["fastdata", "fastcode", "data_" + cont_ram.contains[0].name], + sections_bss=["bss_" + cont_ram.contains[0].name], sections_noinit=["faststack"]) }} {{ linker.all_heap_sections(table_copy, table_zero, table_heap) }} From 64fc5e6ccbab12610ea145792d0401e016fc7e44 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 17 Jul 2023 18:30:51 +0200 Subject: [PATCH 027/159] [math] Adding long vector typedefs --- src/modm/math/geometry/vector3.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modm/math/geometry/vector3.hpp b/src/modm/math/geometry/vector3.hpp index 6287ee671b..e140b8e4ca 100644 --- a/src/modm/math/geometry/vector3.hpp +++ b/src/modm/math/geometry/vector3.hpp @@ -211,6 +211,8 @@ namespace modm typedef Vector Vector3f; typedef Vector Vector3i; typedef Vector Vector3u; + typedef Vector Vector3li; + typedef Vector Vector3lu; } #include "vector3_impl.hpp" From 7bdcab4be6e7c5ddeff6ca71d663c9043543c039 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 17 Jul 2023 18:56:11 +0200 Subject: [PATCH 028/159] [driver] Adding InvenSense 6-Axis IMU driver --- README.md | 19 +- src/modm/driver/inertial/ixm42xxx.hpp | 158 +++ src/modm/driver/inertial/ixm42xxx.lb | 47 + src/modm/driver/inertial/ixm42xxx_data.hpp | 105 ++ .../driver/inertial/ixm42xxx_definitions.hpp | 936 ++++++++++++++++++ src/modm/driver/inertial/ixm42xxx_impl.hpp | 191 ++++ .../driver/inertial/ixm42xxx_transport.hpp | 105 ++ .../inertial/ixm42xxx_transport_impl.hpp | 135 +++ 8 files changed, 1687 insertions(+), 9 deletions(-) create mode 100644 src/modm/driver/inertial/ixm42xxx.hpp create mode 100644 src/modm/driver/inertial/ixm42xxx.lb create mode 100644 src/modm/driver/inertial/ixm42xxx_data.hpp create mode 100644 src/modm/driver/inertial/ixm42xxx_definitions.hpp create mode 100644 src/modm/driver/inertial/ixm42xxx_impl.hpp create mode 100644 src/modm/driver/inertial/ixm42xxx_transport.hpp create mode 100644 src/modm/driver/inertial/ixm42xxx_transport_impl.hpp diff --git a/README.md b/README.md index 5c6fd1cb21..579f3d93d7 100644 --- a/README.md +++ b/README.md @@ -751,68 +751,69 @@ your specific needs. IS31FL3733 ITG3200 +IXM42XXX L3GD20 LAN8720A LAWICEL LIS302DL -LIS3DSH +LIS3DSH LIS3MDL LM75 LP503x LSM303A LSM6DS33 -LSM6DSO +LSM6DSO LTC2984 MAX31855 MAX31865 MAX6966 MAX7219 -MCP23x17 +MCP23x17 MCP2515 MCP3008 MCP7941x MCP990X MMC5603 -MS5611 +MS5611 MS5837 NOKIA5110 NRF24 TFT-DISPLAY PAT9125EL -PCA8574 +PCA8574 PCA9535 PCA9548A PCA9685 SH1106 SIEMENS-S65 -SIEMENS-S75 +SIEMENS-S75 SK6812 SK9822 SSD1306 ST7586S ST7789 -STTS22H +STTS22H STUSB4500 SX1276 TCS3414 TCS3472 TLC594x -TMP102 +TMP102 TMP12x TMP175 TOUCH2046 VL53L0 VL6180 -WS2812 +WS2812 diff --git a/src/modm/driver/inertial/ixm42xxx.hpp b/src/modm/driver/inertial/ixm42xxx.hpp new file mode 100644 index 0000000000..daed1c8155 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx.hpp @@ -0,0 +1,158 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_HPP +#define MODM_IXM42XXX_HPP + +#include "ixm42xxx_data.hpp" +#include "ixm42xxx_definitions.hpp" +#include "ixm42xxx_transport.hpp" + +namespace modm +{ + +/** + * @tparam Transport Either the I2C or SPI Transport Layer. + * @see Ixm42xxxTransportI2c + * @see Ixm42xxxTransportSpi + * + * @ingroup modm_driver_ixm42xxx + * @author Rasmus Kleist Hørlyck Sørensen + */ +template < class Transport > +class Ixm42xxx : public ixm42xxx, public Transport +{ +private: + using Data = modm::ixm42xxxdata::Data; + +public: + /** + * @brief Constructor + * + * @param data A ixm42xxx::data object + * @param address the I2C address of the device + */ + Ixm42xxx(Data &data, uint8_t address = 0b11010000); + + /** + * @brief Initialize the device to use default settings and the endianess of the microcontroller + * @warning Calling this functions resets the device and blocks for 1 ms + */ + modm::ResumableResult + initialize(); + + /** + * @brief Read the temperature data from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readTempData(); + + /** + * @brief Read the accel data from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readAccelData(); + + /** + * @brief Read the gyro data from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readGyroData(); + + /** + * @brief Read the sensor data from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readSensorData(); + +public: + /** + * @brief update a single register. + * + * @param reg The register to be updated + * @param setMask The bits to be setted in the register + * @param clearMask The bits to be cleared in the register. + * @return False in case of any error, e.g. if some register access is not + * permitted. + * + * @warning Only the registers GYRO_CONFIG0, ACCEL_CONFIG0 and PWR_MGMT0 + * can be modified during sensor operation + */ + modm::ResumableResult + updateRegister(Register reg, Register_t setMask, Register_t clearMask = Register_t(0xff)); + + /** + * @brief Write a single register. + * + * @param reg The register to be read + * @param value The value to write to the register + * @return False in case of any error, e.g. if some register access is not + * permitted. + * + * @warning Only the registers GYRO_CONFIG0, ACCEL_CONFIG0 and PWR_MGMT0 + * can be modified during sensor operation + */ + modm::ResumableResult + writeRegister(Register reg, uint8_t value); + + /** + * @brief Read a single register. + * + * @param reg The register to be read + * @param value The placeholder for the read value to be stored + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readRegister(Register reg, uint8_t *value); + + /** + * @brief Read consecutive registers. + * + * @param reg The register to start reading from + * @param data The placeholder for the read values to be stored + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readRegister(Register reg, uint8_t *buffer, std::size_t length); + +public: + /// Get the data object for this sensor. + inline Data& + getData() + { return data; } + +protected: + inline modm::ResumableResult + setRegisterBank(Register regi); + +private: + Data &data; + uint8_t readByte; + uint8_t prevBank; +}; + +} // namespace modm + +#include "ixm42xxx_impl.hpp" + +#endif // MODM_IXM42XXX_HPP \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx.lb b/src/modm/driver/inertial/ixm42xxx.lb new file mode 100644 index 0000000000..a919a2cb17 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx.lb @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen +# +# 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 = ":driver:ixm42xxx" + module.description = """\ +# InvenSense 6-Axis IMU + +High Precision 6-Axis MEMS MotionTracking Device + +!!! note "The registers in user bank 1, 2, 3 and 4 are currently not mapped out" + +!!! note "The IXM-42xxx driver has been developed for IIM-42652" + Some functionality may not be implemented or may not be applicable for other InvenSense 6-Axis IMU + +!!! warning "The IXM-42xxx driver I2C transport layer is untested" + +""" + +def prepare(module, options): + module.depends( + ":architecture:register", + ":architecture:i2c.device", + ":architecture:spi.device", + ":math:geometry", + ":math:utils", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/inertial" + env.copy("ixm42xxx.hpp") + env.copy("ixm42xxx_impl.hpp") + env.copy("ixm42xxx_data.hpp") + env.copy("ixm42xxx_definitions.hpp") + env.copy("ixm42xxx_transport.hpp") + env.copy("ixm42xxx_transport_impl.hpp") diff --git a/src/modm/driver/inertial/ixm42xxx_data.hpp b/src/modm/driver/inertial/ixm42xxx_data.hpp new file mode 100644 index 0000000000..08c83b491a --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_data.hpp @@ -0,0 +1,105 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_DATA_HPP +#define MODM_IXM42XXX_DATA_HPP + + +#include + +namespace modm +{ + +template < class Transport > +class Ixm42xxx; + +namespace ixm42xxxdata +{ + +/// @ingroup modm_driver_ixm42xxx +struct +Data +{ + template < class Transport > + friend class ::modm::Ixm42xxx; + + Data() : + accelScale(16.f), + gyroScale(2000.f) + { + } + + // DATA ACCESS + ///@{ + int16_t inline + getTemp() const + { return sensorData.temp; } + + void inline + getTemp(int16_t *temp) const + { *temp = getTemp(); } + + void inline + getTemp(float *temp) const + { float t = getTemp(); *temp = t / 132.48f + 25.f; } + + Vector3i inline + getAccel() const + { return Vector3i(sensorData.accel); } + + void inline + getAccel(Vector3i *accel) const + { Vector3li a = getAccel(); *accel = a; } + + void inline + getAccel(Vector3f *accel) const + { Vector3f a = getAccel(); a *= accelScale / INT16_MAX; *accel = a; } + + Vector3i inline + getGyro() const + { return Vector3i(sensorData.gyro); } + + void inline + getGyro(Vector3i *gyro) const + { *gyro = getGyro(); } + + void inline + getGyro(Vector3f *gyro) const + { Vector3f g = getGyro(); g *= gyroScale / INT16_MAX; *gyro = g; } + ///@} + + constexpr float + getAccelScale() const + { return accelScale; } + + constexpr float + getGyroScale() const + { return gyroScale; } + +private: + struct + SensorData { + int16_t temp; + int16_t accel[3]; + int16_t gyro[3]; + } sensorData; + + float accelScale; + float gyroScale; +}; + +} // ixm42xxxdata namespace + +} // namespace modm + +#endif // MODM_IXM42XXX_DATA_HPP \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx_definitions.hpp b/src/modm/driver/inertial/ixm42xxx_definitions.hpp new file mode 100644 index 0000000000..e5c95159b5 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_definitions.hpp @@ -0,0 +1,936 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_DEFINITIONS_HPP +#define MODM_IXM42XXX_DEFINITIONS_HPP + +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_ixm42xxx +struct ixm42xxx +{ + enum class + Register : uint16_t + { + REG_BANK_SEL = 0x76, + + // USER BANK 0 REGISTER MAP START + // *** ORed with 0x000 to indicate user bank 0 *** + DEVICE_CONFIG = 0x011, + DRIVE_CONFIG = 0x013, + INT_CONFIG = 0x014, + FIFO_CONFIG = 0x016, + + // Temperature data registers + TEMP_DATA1 = 0x01D, + TEMP_DATA0 = 0x01E, + + // Accel data registers + ACCEL_DATA_X1 = 0x01F, + ACCEL_DATA_X0 = 0x020, + ACCEL_DATA_Y1 = 0x021, + ACCEL_DATA_Y0 = 0x022, + ACCEL_DATA_Z1 = 0x023, + ACCEL_DATA_Z0 = 0x024, + + // Gyro data registers + GYRO_DATA_X1 = 0x025, + GYRO_DATA_X0 = 0x026, + GYRO_DATA_Y1 = 0x027, + GYRO_DATA_Y0 = 0x028, + GYRO_DATA_Z1 = 0x029, + GYRO_DATA_Z0 = 0x02A, + + TMST_FSYNCH = 0x02B, + TMST_FSYNCL = 0x02C, + INT_STATUS = 0x02D, + + // FIFO data registers + FIFO_COUNTH = 0x02E, + FIFO_COUNTL = 0x02F, + FIFO_DATA = 0x030, + + // Apex data register + APEX_DATA0 = 0x031, + APEX_DATA1 = 0x032, + APEX_DATA2 = 0x033, + APEX_DATA3 = 0x034, + APEX_DATA4 = 0x035, + APEX_DATA5 = 0x036, + + INT_STATUS2 = 0x037, + INT_STATUS3 = 0x038, + + SIGNAL_PATH_RESET = 0x04B, + + INTF_CONFIG0 = 0x04C, + INTF_CONFIG1 = 0x04D, + + PWR_MGMT0 = 0x04E, + GYRO_CONFIG0 = 0x04F, + ACCEL_CONFIG0 = 0x050, + GYRO_CONFIG1 = 0x051, + GYRO_ACCEL_CONFIG0 = 0x052, + ACCEL_CONFIG1 = 0x053, + + TMST_CONFIG = 0x054, + APEX_CONFIG0 = 0x056, + SMD_CONFIG = 0x057, + + // FIFO configuration registers + FIFO_CONFIG1 = 0x05F, + FIFO_CONFIG2 = 0x060, + FIFO_CONFIG3 = 0x061, + FSYNC_CONFIG = 0x062, + + // Interrupt configuration registers + INT_CONFIG0 = 0x063, + INT_CONFIG1 = 0x064, + + // Interrupt source registers + INT_SOURCE0 = 0x065, + INT_SOURCE1 = 0x066, + INT_SOURCE3 = 0x068, + INT_SOURCE4 = 0x069, + + FIFO_LOST_PKT0 = 0x06C, + FIFO_LOST_PKT1 = 0x06D, + + SELF_TEST_CONFIG = 0x070, + WHO_AM_I = 0x075, + // USER BANK 0 REGISTER MAP END + + // USER BANK 1 REGISTER MAP START + // *** ORed with 0x100 to indicate user bank 1 *** + SENSOR_CONFIG0 = 0x103, + GYRO_CONFIG_STATIC2 = 0x10B, + GYRO_CONFIG_STATIC3 = 0x10C, + GYRO_CONFIG_STATIC4 = 0x10D, + GYRO_CONFIG_STATIC5 = 0x10E, + GYRO_CONFIG_STATIC6 = 0x10F, + GYRO_CONFIG_STATIC7 = 0x110, + GYRO_CONFIG_STATIC8 = 0x111, + GYRO_CONFIG_STATIC9 = 0x112, + GYRO_CONFIG_STATIC10 = 0x113, + + XG_ST_DATA = 0x15F, + YG_ST_DATA = 0x160, + ZG_ST_DATA = 0x161, + + TMSTVAL0 = 0x162, + TMSTVAL1 = 0x163, + TMSTVAL2 = 0x164, + + INTF_CONFIG4 = 0x17A, + INTF_CONFIG5 = 0x17B, + INTF_CONFIG6 = 0x17C, + // USER BANK 1 REGISTER MAP END + + // USER BANK 2 REGISTER MAP START + // *** ORed with 0x200 to indicate user bank 2 *** + ACCEL_CONFIG_STATIC2 = 0x203, + ACCEL_CONFIG_STATIC3 = 0x204, + ACCEL_CONFIG_STATIC4 = 0x205, + + // Accelerometer self-test data + XA_ST_DATA = 0x23B, + YA_ST_DATA = 0x23C, + ZA_ST_DATA = 0x23D, + // USER BANK 2 REGISTER MAP END + + // USER BANK 3 REGISTER MAP START + // *** ORed with 0x300 to indicate user bank 3 *** + PU_PD_CONFIG1 = 0x306, + PU_PD_CONFIG2 = 0x30E, + // USER BANK 3 REGISTER MAP END + + // USER BANK 4 REGISTER MAP START + // *** ORed with 0x400 to indicate user bank 4 *** + // Apex configuration registers + FDR_CONFIG = 0x409, + APEX_CONFIG1 = 0x440, + APEX_CONFIG2 = 0x441, + APEX_CONFIG3 = 0x442, + APEX_CONFIG4 = 0x443, + APEX_CONFIG5 = 0x444, + APEX_CONFIG6 = 0x445, + APEX_CONFIG7 = 0x446, + APEX_CONFIG8 = 0x447, + APEX_CONFIG9 = 0x448, + APEX_CONFIG10 = 0x449, + + // Wake on motion configuration registers + ACCEL_WOM_X_THR = 0x44A, + ACCEL_WOM_Y_THR = 0x44B, + ACCEL_WOM_Z_THR = 0x44C, + + // Interupt sources registers + INT_SOURCE6 = 0x44D, + INT_SOURCE7 = 0x44E, + INT_SOURCE8 = 0x44F, + INT_SOURCE9 = 0x450, + INT_SOURCE10 = 0x451, + + // User programable offsets registers + OFFSET_USER0 = 0x477, + OFFSET_USER1 = 0x478, + OFFSET_USER2 = 0x479, + OFFSET_USER3 = 0x47A, + OFFSET_USER4 = 0x47B, + OFFSET_USER5 = 0x47C, + OFFSET_USER6 = 0x47D, + OFFSET_USER7 = 0x47E, + OFFSET_USER8 = 0x47F, + // USER BANK 4 REGISTER MAP END + }; + +public: + + /// REG_BANK_SEL reset value is 0x00 + enum class + RegBankSel : uint8_t + { + BANK_SEL2 = Bit2, ///< See BankSel_t + BANK_SEL1 = Bit1, ///< See BankSel_t + BANK_SEL0 = Bit0, ///< See BankSel_t + }; + MODM_FLAGS8(RegBankSel); + + enum class + BankSel : uint8_t + { + Bank0 = 0b000, + Bank1 = 0b001, + Bank2 = 0b010, + Bank3 = 0b011, + Bank4 = 0b100, + }; + typedef modm::Configuration BankSel_t; + + // ------------------------------------------------------------------------------- + + /// DEVICE_CONFIG reset value is 0x00 + enum class + DeviceConfig : uint8_t + { + SPI_MODE = Bit4, ///< SPI mode selection + SOFT_RESET_CONFIG = Bit0, ///< Software reset configuration + }; + MODM_FLAGS8(DeviceConfig); + + enum class + DataMode : uint8_t + { + Mode0 = 0, + Mode1 = int(DeviceConfig::SPI_MODE), + Mode2 = int(DeviceConfig::SPI_MODE), + Mode3 = 0, + }; + typedef modm::Configuration DataMode_t; + + enum class + SoftResetConfig : uint8_t + { + Normal = 0, + Enable = int(DeviceConfig::SOFT_RESET_CONFIG), + }; + typedef modm::Configuration SoftResetConfig_t; + + // ------------------------------------------------------------------------------- + + /// DRIVE_CONFIG reset value is 0x05 + enum class + DriveConfig : uint8_t + { + I2C_SLEW_RATE2 = Bit5, ///< See I2cSlewRate_t + I2C_SLEW_RATE1 = Bit4, ///< See I2cSlewRate_t + I2C_SLEW_RATE0 = Bit3, ///< See I2cSlewRate_t + SPI_SLEW_RATE2 = Bit2, ///< See SpiSlewRate_t + SPI_SLEW_RATE1 = Bit1, ///< See SpiSlewRate_t + SPI_SLEW_RATE0 = Bit0, ///< See SpiSlewRate_t + }; + MODM_FLAGS8(DriveConfig); + + enum class + SlewRate : uint8_t + { + ns20ns60 = 0, + ns12ns36 = int(DriveConfig::SPI_SLEW_RATE0), + ns6ns18 = int(DriveConfig::SPI_SLEW_RATE1), + ns4ns12 = int(DriveConfig::SPI_SLEW_RATE1) | int(DriveConfig::SPI_SLEW_RATE0), + ns2ns6 = int(DriveConfig::SPI_SLEW_RATE2), + ns2 = int(DriveConfig::SPI_SLEW_RATE2) | int(DriveConfig::SPI_SLEW_RATE0), + }; + typedef modm::Configuration I2cSlewRate_t; + typedef modm::Configuration SpiSlewRate_t; + + // ------------------------------------------------------------------------------- + + /// INT_CONFIG reset value is 0x00 + enum class + IntConfig : uint8_t + { + INT2_MODE = Bit5, ///< INT2 pulsed/latched interrupt mode + INT2_DRIVE_CIRCUIT = Bit4, ///< INT2 open drain/push pull drive circuit + INT2_POLARITY = Bit3, ///< INT2 active low/high interrupt polarity + INT1_MODE = Bit2, ///< INT1 pulsed/latched interrupt mode + INT1_DRIVE_CIRCUIT = Bit1, ///< INT1 open drain/push pull drive circuit + INT1_POLARITY = Bit0, ///< INT1 active low/high interrupt polarity + }; + MODM_FLAGS8(IntConfig); + + // ------------------------------------------------------------------------------- + + /// FIFO_CONFIG reset value is 0x00 + enum class + FifoConfig : uint8_t + { + FIFO_MODE1 = Bit7, ///< See FifoMode_t + FIFO_MODE0 = Bit6, ///< See FifoMode_t + }; + MODM_FLAGS8(FifoConfig); + + enum class + FifoMode : uint8_t + { + Bypass = 0, + StreamToFifo = int(FifoConfig::FIFO_MODE0), + StopOnFull = int(FifoConfig::FIFO_MODE1), + }; + typedef modm::Configuration FifoMode_t; + + // ------------------------------------------------------------------------------- + + /// INT_STATUS reset value is 0x10 + enum class + IntStatus : uint8_t + { + UI_FSYNC_INT = Bit6, ///< UI FSYNC Interrupt, clears on read + PLL_RDY_INT = Bit5, ///< PLL Ready Interrupt, clears on read + RESET_DONE_INT = Bit4, ///< Software Reset Complete Interrupt, clears on read + DATA_RDY_INT = Bit3, ///< Data Ready Interrupt, clears on read + FIFO_THS_INT = Bit2, ///< FIFO Buffer Threshold Interrupt, clears on read + FIFO_FULL_INT = Bit1, ///< FIFO Buffer Full Interrupt, clears on read + AGC_RDY_INT = Bit0, ///< AGC Ready Interrupt, clears on read + }; + MODM_FLAGS8(IntStatus); + + // ------------------------------------------------------------------------------- + + /// INT_STATUS2 reset value is 0x00 + enum class + IntStatus2 : uint8_t + { + SMD_INT = Bit3, ///< Significant Motion Detection Interrupt, clears on read + WOM_Z_INT = Bit2, ///< Wake on Motion Interrupt on Z-axis, clears on read + WOM_Y_INT = Bit1, ///< Wake on Motion Interrupt on Y-axis, clears on read + WOM_X_INT = Bit0, ///< Wake on Motion Interrupt on X-axis, clears on read + }; + MODM_FLAGS8(IntStatus2); + + // ------------------------------------------------------------------------------- + + /// INT_STATUS3 reset value is 0x00 + enum class + IntStatus3 : uint8_t + { + STEP_DET_INT = Bit5, ///< Step Detection Interrupt, clears on read + STEP_CNT_OVF_INT = Bit4, ///< Step Count Overflow Interrupt, clears on read + TILT_DET_INT = Bit3, ///< Tilt Detection Interrupt, clears on read + FF_DET_INT = Bit1, ///< Freefall Interrupt, clears on read + TAP_DET_INT = Bit0, ///< Tap Detection Interrupt, clears on read + }; + MODM_FLAGS8(IntStatus3); + + // ------------------------------------------------------------------------------- + + /// SIGNAL_PATH_RESET reset value is 0x00 + enum class + SignalPathReset : uint8_t + { + DMP_INIT_EN = Bit6, ///< DMP enable/disable + DMP_MEM_RESET_EN = Bit5, ///< DMP memory reset enable/disable + ABORT_AND_RESET = Bit3, ///< Signal path reset enable/disable + TMST_STROBE = Bit2, ///< Strobe enable/disable + FIFO_FLUSH = Bit1, ///< FIFO flush enable/disable + }; + MODM_FLAGS8(SignalPathReset); + + // ------------------------------------------------------------------------------- + + /// INTF_CONFIG0 reset value is 0x30 + enum class + IntfConfig0 : uint8_t + { + FIFO_HOLD_LAST_DATA_EN = Bit7, + FIFO_COUNT_REC = Bit6, ///< FIFO count is reported in records enable/disable + FIFO_COUNT_ENDIAN = Bit5, ///< FIFO count is reported in Little/Big Endian format + SENSOR_DATA_ENDIAN = Bit4, ///< Sensor data is reported in Little/Big Endian + UI_SIFS_CFG1 = Bit1, ///< See UiSifsCfg_t + UI_SIFS_CFG0 = Bit0, ///< See UiSifsCfg_t + }; + MODM_FLAGS8(IntfConfig0); + + enum class + UiSifsCfg : uint8_t + { + DisableSpi = int(IntfConfig0::UI_SIFS_CFG1), + DisableI2c = int(IntfConfig0::UI_SIFS_CFG1) | int(IntfConfig0::UI_SIFS_CFG0), + }; + typedef modm::Configuration UiSifsCfg_t; + + // ------------------------------------------------------------------------------- + + /// INTF_CONFIG1 reset value is 0x91 + enum class + IntfConfig1 : uint8_t + { + ACCEL_LP_CLK_SEL = Bit3, ///< Accelerometer LP mode uses Wake Up/RC oscillator clock + RTC_MODE = Bit2, ///< Require input RTC clock + CLK_SEL1 = Bit1, ///< See ClkSel_t + CLK_SEL0 = Bit0, ///< See ClkSel_t + }; + MODM_FLAGS8(IntfConfig1); + + enum class + ClkSel : uint8_t + { + Rc = 0, + Pll = int(IntfConfig1::CLK_SEL0), + Disable = int(IntfConfig1::CLK_SEL1) | int(IntfConfig1::CLK_SEL0), + }; + typedef modm::Configuration ClkSel_t; + + // ------------------------------------------------------------------------------- + + /// PWR_MGMT0 reset value is 0x00 + enum class + PwrMgmt0 : uint8_t + { + TEMP_DIS = Bit5, ///< Temperature sensor is enable/disable + IDLE = Bit4, ///< Idle mode is enable/disable + GYRO_MODE1 = Bit3, ///< See GyroMode_t + GYRO_MODE0 = Bit2, ///< See GyroMode_t + ACCEL_MODE1 = Bit1, ///< See AccelMode_t + ACCEL_MODE0 = Bit0, ///< See AccelMode_t + }; + MODM_FLAGS8(PwrMgmt0); + + enum class + GyroMode : uint8_t + { + Off = 0, + Standby = int(PwrMgmt0::GYRO_MODE0), + LowNoise = int(PwrMgmt0::GYRO_MODE1) | int(PwrMgmt0::GYRO_MODE0), + }; + typedef modm::Configuration GyroMode_t; + + enum class + AccelMode : uint8_t + { + Off = 0, + LowPower = int(PwrMgmt0::ACCEL_MODE1), + LowNoise = int(PwrMgmt0::ACCEL_MODE1) | int(PwrMgmt0::ACCEL_MODE0), + }; + typedef modm::Configuration AccelMode_t; + + // ------------------------------------------------------------------------------- + + /// GYRO_CONFIG0 reset value is 0x06 + enum class + GyroConfig0 : uint8_t + { + GYRO_FS_SEL2 = Bit7, ///< See GyroFs_t + GYRO_FS_SEL1 = Bit6, ///< See GyroFs_t + GYRO_FS_SEL0 = Bit5, ///< See GyroFs_t + GYRO_ODR3 = Bit3, ///< See GyroOdr_t + GYRO_ODR2 = Bit2, ///< See GyroOdr_t + GYRO_ODR1 = Bit1, ///< See GyroOdr_t + GYRO_ODR0 = Bit0, ///< See GyroOdr_t + }; + MODM_FLAGS8(GyroConfig0); + + enum class + GyroFs: uint8_t + { + dps2000 = 0b000, + dps1000 = 0b001, + dps500 = 0b010, + dps250 = 0b011, + dps125 = 0b100, + dps62_5 = 0b101, + dps31_25 = 0b110, + dps15_625 = 0b111, + }; + typedef modm::Configuration GyroFs_t; + + enum class + GyroOdr: uint8_t + { + kHz32 = 0b0001, + kHz16 = 0b0010, + kHz8 = 0b0011, + kHz4 = 0b0100, + kHz2 = 0b0101, + kHz1 = 0b0110, + Hz200 = 0b0111, + Hz100 = 0b1000, + Hz50 = 0b1001, + Hz25 = 0b1010, + Hz12_5 = 0b1011, + Hz500 = 0b1111, + }; + typedef modm::Configuration GyroOdr_t; + + // ------------------------------------------------------------------------------- + + /// ACCEL_CONFIG0 reset value is 0x06 + enum class + AccelConfig0 : uint8_t + { + ACCEL_FS_SEL2 = Bit7, ///< See AccelFs_t + ACCEL_FS_SEL1 = Bit6, ///< See AccelFs_t + ACCEL_FS_SEL0 = Bit5, ///< See AccelFs_t + ACCEL_ODR3 = Bit3, ///< See AccelOdr_t + ACCEL_ODR2 = Bit2, ///< See AccelOdr_t + ACCEL_ODR1 = Bit1, ///< See AccelOdr_t + ACCEL_ODR0 = Bit0, ///< See AccelOdr_t + }; + MODM_FLAGS8(AccelConfig0); + + enum class + AccelFs : uint8_t + { + g16 = 0b000, ///< ±16g + g8 = 0b001, ///< ±8g + g4 = 0b010, ///< ±4g + g2 = 0b011, ///< ±2g + }; + typedef modm::Configuration AccelFs_t; + + enum class + AccelOdr: uint8_t + { + kHz32 = 0b0001, ///< 32 kHz + kHz16 = 0b0010, ///< 16 kHz + kHz8 = 0b0011, ///< 8 kHz (LN mode) + kHz4 = 0b0100, ///< 4 kHz (LN mode) + kHz2 = 0b0101, ///< 2 kHz (LN mode) + kHz1 = 0b0110, ///< 1 kHz (LN mode) (default) + Hz200 = 0b0111, ///< 200 Hz (LN or LP mode) + Hz100 = 0b1000, ///< 100 Hz (LN or LP mode) + Hz50 = 0b1001, ///< 50 Hz (LN or LP mode) + Hz25 = 0b1010, ///< 25 Hz (LN or LP mode) + Hz12_5 = 0b1011, ///< 12.5 Hz (LN or LP mode) + Hz6_25 = 0b1100, ///< 6.25 Hz (LP mode) + Hz3_125 = 0b1101, ///< 3.125 Hz (LP mode) + Hz1_5625 = 0b1110, ///< 1.5625 Hz (LP mode) + Hz500 = 0b1111, ///< 500 Hz (LN or LP mode) + }; + typedef modm::Configuration AccelOdr_t; + + // ------------------------------------------------------------------------------- + + /// GYRO_CONFIG1 reset value is 0x16 + enum class + GyroConfig1 : uint8_t + { + TEMP_FILTER_BW2 = Bit7, ///< See TempFiltBw_t + TEMP_FILTER_BW1 = Bit6, ///< See TempFiltBw_t + TEMP_FILTER_BW0 = Bit5, ///< See TempFiltBw_t + GYRO_UI_FILTER_ORD1 = Bit3, ///< See GyroUiFiltOrd_t + GYRO_UI_FILTER_ORD0 = Bit2, ///< See GyroUiFiltOrd_t + GYRO_DEC2_M2_ORD1 = Bit1, ///< See GyroDec2M2Ord_t + GYRO_DEC2_M2_ORD0 = Bit0, ///< See GyroDec2M2Ord_t + }; + MODM_FLAGS8(GyroConfig1); + + enum class + TempFiltBw : uint8_t + { + Hz4000 = 0b000, + Hz170 = 0b001, + Hz82 = 0b010, + Hz40 = 0b011, + Hz20 = 0b100, + Hz10 = 0b101, + Hz5 = 0b110, + }; + typedef modm::Configuration TempFiltBw_t; + + enum class + GyroUiFiltOrd : uint8_t + { + First = 0, + Second = int(GyroConfig1::GYRO_UI_FILTER_ORD0), + Third = int(GyroConfig1::GYRO_UI_FILTER_ORD1), + }; + typedef modm::Configuration GyroUiFiltOrd_t; + + enum class + GyroDec2M2Ord : uint8_t + { + Third = int(GyroConfig1::GYRO_DEC2_M2_ORD1), + }; + typedef modm::Configuration GyroDec2M2Ord_t; + + // ------------------------------------------------------------------------------- + + /// GYRO_ACCEL_CONFIG0 reset value is 0x11 + enum class + GyroAccelCongfig0 : uint8_t + { + ACCEL_UI_FILTER_BW3 = Bit7, ///< See AccelUiFilterBw_t + ACCEL_UI_FILTER_BW2 = Bit6, ///< See AccelUiFilterBw_t + ACCEL_UI_FILTER_BW1 = Bit5, ///< See AccelUiFilterBw_t + ACCEL_UI_FILTER_BW0 = Bit4, ///< See AccelUiFilterBw_t + GRYO_UI_FILTER_BW3 = Bit3, ///< See GyroUiFilterBw_t + GRYO_UI_FILTER_BW2 = Bit2, ///< See GyroUiFilterBw_t + GRYO_UI_FILTER_BW1 = Bit1, ///< See GyroUiFilterBw_t + GRYO_UI_FILTER_BW0 = Bit0, ///< See GyroUiFilterBw_t + }; + MODM_FLAGS8(GyroAccelCongfig0); + + enum class + UiFiltBw : uint8_t + { + /// Low Noise Mode: + Div2 = 0, ///< BW = ODR / 2 + Div4 = 1, ///< BW = max(400 Hz, ODR) / 2 (default) + Div5 = 2, ///< BW = max(400 Hz, ODR) / 5 + Div8 = 3, ///< BW = max(400 Hz, ODR) / 8 + Div10 = 4, ///< BW = max(400 Hz, ODR) / 10 + Div16 = 5, ///< BW = max(400 Hz, ODR) / 16 + Div20 = 6, ///< BW = max(400 Hz, ODR) / 20 + Div40 = 7, ///< BW = max(400 Hz, ODR) / 40 + LowLatency1 = 14, ///< Dec2 runs at max(400 Hz, ODR) + LowLatency2 = 15, ///< Dec2 runs at max(200 Hz, 8 * ODR) + + /// Low Power Mode: + Avg1 = 1, ///< 1x AVG filter (default) + Avg16 = 6, ///< 16x AVG filter + }; + typedef modm::Configuration AccelUiFiltBw_t; + typedef modm::Configuration GyroUiFiltBw_t; + + // ------------------------------------------------------------------------------- + + /// ACCEL_CONFIG1 reset value is 0x0D + enum class + AccelConfig1 : uint8_t + { + ACCEL_UI_FILTER_ORD1 = Bit4, ///< See AccelUiFiltOrd_t + ACCEL_UI_FILTER_ORD0 = Bit3, ///< See AccelUiFiltOrd_t + ACCEL_DEC2_M2_ORD1 = Bit2, ///< See AccelDec2M2Ord_t + ACCEL_DEC2_M2_ORD0 = Bit1, ///< See AccelDec2M2Ord_t + }; + MODM_FLAGS8(AccelConfig1); + + enum class + AccelUiFiltOrd : uint8_t + { + First = 0, + Second = int(AccelConfig1::ACCEL_UI_FILTER_ORD0), + Third = int(AccelConfig1::ACCEL_UI_FILTER_ORD1), + }; + typedef modm::Configuration AccelUiFiltOrd_t; + + enum class + AccelDec2M2Ord : uint8_t + { + Third = int(AccelConfig1::ACCEL_DEC2_M2_ORD1), + }; + typedef modm::Configuration AccelDec2M2Ord_t; + + // ------------------------------------------------------------------------------- + + /// TMST_CONFIG reset value is 0x23 + enum class + TmstConfig : uint8_t + { + TMST_TO_REGS_EN = Bit4, ///< Time stamp to register enable/disable + TMST_RES = Bit3, ///< Time stamp resolution 1 us/16 us or one RTC clock period + TMST_DELTA_EN = Bit2, ///< Time stamp delta enable/disable + TMST_FSYNC_EN = Bit1, ///< Time stamp register FSYNC enable/disable + TMST_EN = Bit0, ///< Time stamp register enable/disable + }; + MODM_FLAGS8(TmstConfig); + + // ------------------------------------------------------------------------------- + + /// APEX_CONFIG0 reset value is 0x82 + enum class + ApexConfig0 : uint8_t + { + DMP_POWER_SAVE = Bit7, ///< DMP power save mode enable/disable + TAP_ENABLE = Bit6, ///< Tap detection enable/disable + PED_ENABLE = Bit5, ///< Pedometer enable/disable + TILT_ENABLE = Bit4, ///< Tile detection enable/disable + FF_ENABLE = Bit2, ///< Freefall detection enable/disable + DMP_ODR1 = Bit1, ///< See DmpOdr_t + DMP_ODR0 = Bit0, ///< See DmpOdr_t + }; + MODM_FLAGS8(ApexConfig0); + + enum class + DmpOdr : uint8_t + { + Hz25 = 0, + Hz500 = int(ApexConfig0::DMP_ODR0), + Hz50 = int(ApexConfig0::DMP_ODR1), + Hz100 = int(ApexConfig0::DMP_ODR1) | int(ApexConfig0::DMP_ODR0), + }; + typedef modm::Configuration DmpOdr_t; + + // ------------------------------------------------------------------------------- + + /// SMD_CONFIG reset value is 0x00 + enum class + SmdConfig : uint8_t + { + WOM_INT_MODE = Bit3, ///< Set WoM interrupt on the OR/AND of all enabled accelerometer thresholds + WOM_MODE = Bit2, ///< Compare samples to initial/previous sample + SMD_MODE1 = Bit1, ///< See SmdMode_t + SMD_MODE0 = Bit0, ///< See SmdMode_t + }; + MODM_FLAGS8(SmdConfig); + + enum class + SmdMode : uint8_t + { + Disabled = 0, + Wom = int(SmdConfig::SMD_MODE0), + Short = int(SmdConfig::SMD_MODE1), + Long = int(SmdConfig::SMD_MODE1) | int(SmdConfig::SMD_MODE0), + }; + typedef modm::Configuration SmdMode_t; + + // ------------------------------------------------------------------------------- + + /// FIFO_CONFIG1 reset value is 0x00 + enum class + FifoConfig1 : uint8_t + { + FIFO_RESUME_PARTIAL_RD = Bit6, ///< Partial FIFO read enable/disable + FIFO_WM_GT_TH = Bit5, ///< FIFO watermark interrupt enable/disable + FIFO_HIRES_EN = Bit4, ///< FIFO extended resolution enable/disable + FIFO_TMST_FSYNC_EN = Bit3, ///< FIFO FSYNC enable/disable + FIFO_TEMP_EN = Bit2, ///< FIFO temperature data enable/disable + FIFO_GYRO_EN = Bit1, ///< FIFO gyro data enable/disable + FIFO_ACCEL_EN = Bit0, ///< FIFO acdel data enable/disable + }; + MODM_FLAGS8(FifoConfig1); + + // ------------------------------------------------------------------------------- + + /// FSYNC_CONFIG reset value is 0x10 + enum class + FsyncConfig : uint8_t + { + FSYNC_UI_SEL2 = Bit6, ///< See FsyncUiSel_t + FSYNC_UI_SEL1 = Bit5, ///< See FsyncUiSel_t + FSYNC_UI_SEL0 = Bit4, ///< See FsyncUiSel_t + FSYNC_UI_FLAG_CLEAR_SEL = Bit1, ///< FSYNC flag is cleared when UI sensor register is updated/read lsb + FSYNC_POLARITY = Bit0, ///< Start from Rising/Falling edge of FSYNC pulse to measure FSYNC interval + }; + MODM_FLAGS8(FsyncConfig); + + enum class + FsyncUiSel : uint8_t + { + NoTag = 0, + TempOut = 0b001, + GyroXout = 0b010, + GyroYout = 0b011, + GyroZout = 0b100, + AccelXout = 0b101, + AccelYout = 0b110, + AccelZout = 0b111, + }; + typedef modm::Configuration FsyncUiSel_t; + + // ------------------------------------------------------------------------------- + + /// INT_CONFIG0 reset value is 0x00 + enum class + IntConfig0 : uint8_t + { + UI_DRDY_INT_CLEAR1 = Bit5, ///< See UiDrdyIntClear_t + UI_DRDY_INT_CLEAR0 = Bit4, ///< See UiDrdyIntClear_t + FIFO_THS_INT_CLEAR1 = Bit3, ///< See FifoThsIntClear_t + FIFO_THS_INT_CLEAR0 = Bit2, ///< See FifoThsIntClear_t + FIFO_FULL_INT_CLEAR1 = Bit1, ///< See FifoFullIntClear_t + FIFO_FULL_INT_CLEAR0 = Bit0, ///< See FifoFullIntClear_t + }; + MODM_FLAGS8(IntConfig0); + + enum class + UiDrdyIntClear : uint8_t + { + StatusBitRead = 0, + SensorRegisterRead = int(IntConfig0::UI_DRDY_INT_CLEAR1), + Both = int(IntConfig0::UI_DRDY_INT_CLEAR1) | int(IntConfig0::UI_DRDY_INT_CLEAR0), + }; + typedef modm::Configuration UiDrdyIntClear_t; + + enum class + FifoThsIntClear : uint8_t + { + StatusBitRead = 0, + FifoDataRead = int(IntConfig0::FIFO_THS_INT_CLEAR1), + Both = int(IntConfig0::FIFO_THS_INT_CLEAR1) | int(IntConfig0::FIFO_THS_INT_CLEAR0), + }; + typedef modm::Configuration FifoThsIntClear_t; + + enum class + FifoFullIntClear : uint8_t + { + StatusBitRead = 0, + FifoDataRead = int(IntConfig0::FIFO_FULL_INT_CLEAR1), + Both = int(IntConfig0::FIFO_FULL_INT_CLEAR1) | int(IntConfig0::FIFO_FULL_INT_CLEAR0), + }; + typedef modm::Configuration FifoFullIntClear_t; + + // ------------------------------------------------------------------------------- + + /// INT_CONFIG1 reset value is 0x10 + enum class + IntConfig1 : uint8_t + { + INT_TPULSE_DURATION = Bit6, ///< Interrupt pulse duration is 100 us/8 us + INT_TDASSERT_DISABLE = Bit5, ///< De-assert duration enable/disable + INT_ASYNC_RESET = Bit4, ///< Change to 0 for proper INT1 and INT2 pin operation + }; + MODM_FLAGS8(IntConfig1); + + // ------------------------------------------------------------------------------- + + /// INT_SOURCE0 reset value is 0x10 + enum class + IntSource0 : uint8_t + { + UI_FSYNC_INT1_EN = Bit6, ///< UI FSYNC interrupt routed to INT1 enable/disable + PLL_RDY_INT1_EN = Bit5, ///< PLL ready interrupt routed to INT1 enable/disable + REST_DONE_INT1_EN = Bit4, ///< Reset done interrupt routed to INT1 enable/disable + UI_DRDY_INT1_EN = Bit3, ///< UI data ready interrupt routed to INT1 enable/disable + FIFO_THS_INT1_EN = Bit2, ///< FIFO threshold interrupt routed to INT1 enable/disable + FIFO_FULL_INT1_EN = Bit1, ///< FIFO full interrupt routed to INT1 enable/disable + UI_AGC_RDY_INT1_EN = Bit0, ///< UI AGC ready interrupt routed to INT1 enable/disable + }; + MODM_FLAGS8(IntSource0); + + // ------------------------------------------------------------------------------- + + /// INT_SOURCE1 reset value is 0x00 + enum class + IntSource1 : uint8_t + { + I3C_PROTOCOL_ERROR_INT1_EN = Bit6, ///< I3c protocol error interrupt routed to INT1 enable/disable + SMD_INT1_EN = Bit3, ///< SMD interrupt routed to INT1 enable/disable + WOM_Z_INT1_EN = Bit2, ///< Z-axis WOM interrupt routed to INT1 enable/disable + WOM_Y_INT1_EN = Bit1, ///< Y-axis WOM interrupt routed to INT1 enable/disable + WOM_X_INT1_EN = Bit0, ///< X-axis WOM interrupt routed to INT1 enable/disable + }; + MODM_FLAGS8(IntSource1); + + // ------------------------------------------------------------------------------- + + /// INT_SOURCE3 reset value is 0x00 + enum class + IntSource3 : uint8_t + { + UI_FSYNC_INT2_EN = Bit6, ///< UI FSYNC interrupt routed to INT2 enable/disable + PLL_RDY_INT2_EN = Bit5, ///< PLL ready interrupt routed to INT2 enable/disable + RESET_DONE_INT2_EN = Bit4, ///< Reset done interrupt routed to INT2 enable/disable + UI_DRDY_INT2_EN = Bit3, ///< UI data ready interrupt routed to INT2 enable/disable + FIFO_THS_INT2_EN = Bit2, ///< FIFO threshold interrupt routed to INT2 enable/disable + FIFO_FULL_INT2_EN = Bit1, ///< FIFO full interrupt routed to INT2 enable/disable + UI_AGC_RDY_INT2_EN = Bit0, ///< UI AGC ready interrupt routed to INT2 enable/disable + }; + MODM_FLAGS8(IntSource3); + + // ------------------------------------------------------------------------------- + + /// INT_SOURCE4 reset value is 0x00 + enum class + IntSource4 : uint8_t + { + I3C_PROTOCOL_ERROR_INT2_EN = Bit6, ///< I3c protocol error interrupt routed to INT2 enable/disable + SMD_INT2_EN = Bit3, ///< SMD interrupt routed to INT2 enable/disable + WOM_Z_INT2_EN = Bit2, ///< Z-axis WOM interrupt routed to INT2 enable/disable + WOM_Y_INT2_EN = Bit1, ///< Y-axis WOM interrupt routed to INT2 enable/disable + WOM_X_INT2_EN = Bit0, ///< X-axis WOM interrupt routed to INT2 enable/disable + }; + MODM_FLAGS8(IntSource4); + + // ------------------------------------------------------------------------------- + + /// SELF_TEST_CONFIG reset value is 0x00 + enum class + SelfTestConfig : uint8_t + { + ACCEL_ST_POWER = Bit6, ///< Accel self-test enable/disable + EN_AZ_ST = Bit5, ///< Enable Z-accel self-test + EN_AY_ST = Bit4, ///< Enable Y-accel self-test + EN_AX_ST = Bit3, ///< Enable X-accel self-test + EN_GZ_ST = Bit2, ///< Enable Z-gyro self-test + EN_GY_ST = Bit1, ///< Enable Y-gyro self-test + EN_GX_ST = Bit0, ///< Enable X-gyro self-test + }; + MODM_FLAGS8(SelfTestConfig); + +public: + + using Register_t = FlagsGroup< + RegBankSel_t, + DeviceConfig_t, + DriveConfig_t, + IntConfig_t, + FifoConfig_t, + SignalPathReset_t, + IntfConfig0_t, + IntfConfig1_t, + PwrMgmt0_t, + GyroConfig0_t, + AccelConfig0_t, + GyroConfig1_t, + GyroAccelCongfig0_t, + AccelConfig1_t, + TmstConfig_t, + ApexConfig0_t, + SmdConfig_t, + FifoConfig1_t, + FsyncConfig_t, + IntConfig0_t, + IntConfig1_t, + IntSource0_t, + IntSource1_t, + IntSource3_t, + IntSource4_t, + SelfTestConfig_t + >; + +protected: + /// @cond + static constexpr uint8_t + i(Register reg) { return uint8_t(reg); } + /// @endcond +}; + +} // namespace modm + +#endif // MODM_IXM42XXX_DEFINITIONS_HPP \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx_impl.hpp b/src/modm/driver/inertial/ixm42xxx_impl.hpp new file mode 100644 index 0000000000..5b1cccd4a8 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_impl.hpp @@ -0,0 +1,191 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_HPP +# error "Don't include this file directly, use 'ixm42xxx.hpp' instead!" +#endif + +#include "ixm42xxx.hpp" + +namespace modm +{ + +template < class Transport > +Ixm42xxx< Transport >::Ixm42xxx(Data &data, uint8_t address) : Transport(address), data(data) +{ +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::initialize() +{ + RF_BEGIN(); + + /// Synchronize the register bank selection + RF_CALL(readRegister(Register::REG_BANK_SEL, &prevBank)); + + /// Reset the device and wait 1 ms for the reset to be effective + RF_CALL(writeRegister(Register::DEVICE_CONFIG, uint8_t(DeviceConfig::SOFT_RESET_CONFIG))); + modm::delay_ms(1); + + /// Configure the device to use the endianess of the microcontroller + if (isBigEndian()) + { + RF_CALL(writeRegister(Register::INTF_CONFIG0, 0x30)); + } + else + { + RF_CALL(writeRegister(Register::INTF_CONFIG0, 0x0)); + } + + /// Configure the device to use default full scale and odr for gyro and accel + RF_CALL(updateRegister(Register::GYRO_CONFIG0, GyroFs_t(GyroFs::dps1000) | GyroOdr_t(GyroOdr::kHz1))); + RF_CALL(updateRegister(Register::ACCEL_CONFIG0, AccelFs_t(AccelFs::g16) | AccelOdr_t(AccelOdr::kHz1))); + + RF_END(); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readTempData() +{ + return readRegister(Register::TEMP_DATA1, data.sensorData.temp, 2); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readAccelData() +{ + return readRegister(Register::ACCEL_DATA_X1, data.sensorData.accel, 6); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readGyroData() +{ + return readRegister(Register::GYRO_DATA_X1, data.sensorData.gyro, 6); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readSensorData() +{ + return readRegister(Register::TEMP_DATA1, reinterpret_cast(&data.sensorData), 14); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +modm::Ixm42xxx< Transport >::updateRegister(Register reg, Register_t setMask, Register_t clearMask) +{ + RF_BEGIN(); + + if (RF_CALL(setRegisterBank(reg)) && RF_CALL(this->read(i(reg), readByte))) + { + readByte = (readByte & ~clearMask.value) | setMask.value; + if (reg == Register::GYRO_CONFIG0) + { + GyroConfig0_t gyroConfig0 = GyroConfig0_t(readByte); + data.gyroScale = 2000.0f / float(1 << uint8_t(GyroFs_t::get(gyroConfig0))); + } + else if (reg == Register::ACCEL_CONFIG0) + { + AccelConfig0_t accelConfig0 = AccelConfig0_t(readByte); + data.accelScale = 16.0f / float(1 << uint8_t(AccelFs_t::get(accelConfig0))); + } + RF_RETURN_CALL( this->write(i(reg), readByte) ); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +modm::Ixm42xxx< Transport >::writeRegister(Register reg, uint8_t value) +{ + RF_BEGIN(); + + if (RF_CALL(setRegisterBank(reg))) + { + RF_RETURN_CALL( this->write(i(reg), value) ); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +modm::Ixm42xxx< Transport >::readRegister(Register reg, uint8_t *value) +{ + RF_BEGIN(); + + if (RF_CALL(setRegisterBank(reg))) + { + RF_RETURN_CALL( this->read(i(reg), value, 1) ); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +modm::Ixm42xxx< Transport >::readRegister(Register reg, uint8_t *buffer, std::size_t length) +{ + RF_BEGIN(); + + if (RF_CALL(setRegisterBank(reg))) + { + RF_RETURN_CALL( this->read(i(reg), buffer, length) ); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +modm::Ixm42xxx< Transport >::setRegisterBank(Register regi) +{ + const uint8_t bank = (uint16_t(regi) >> 8) & 0xFF; + + RF_BEGIN(); + + if (bank != prevBank) + { + RF_CALL( this->write(i(Register::REG_BANK_SEL), bank) ); + RF_CALL( this->read(i(Register::REG_BANK_SEL), prevBank) ); + RF_RETURN(bank == prevBank); + } + + RF_END_RETURN(true); +} + +} // namespace modm \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx_transport.hpp b/src/modm/driver/inertial/ixm42xxx_transport.hpp new file mode 100644 index 0000000000..caed0ef485 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_transport.hpp @@ -0,0 +1,105 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_TRANSPORT_HPP +#define MODM_IXM42XXX_TRANSPORT_HPP + +#include +#include +#include + +namespace modm +{ + +/** + * IXM42xxx I2C Transport Layer. + * + * @see Ixm42xxx + * + * @tparam I2cMaster I2C interface + * + * @ingroup modm_driver_ixm42xxx_transport + * @author Rasmus Kleist Hørlyck Sørensen + */ +template < class I2cMaster > +class Ixm42xxxTransportI2c : public modm::I2cDevice< I2cMaster, 4 > +{ +public: + Ixm42xxxTransportI2c(uint8_t address); + +protected: + // RAW REGISTER ACCESS + /// write a 8bit value + modm::ResumableResult + write(uint8_t reg, uint8_t value); + + /// read a 8bit value + modm::ResumableResult + read(uint8_t reg, uint8_t &value); + + /// read multiple 8bit values from a start register + modm::ResumableResult + read(uint8_t reg, uint8_t *buffer, std::size_t length); + +private: + uint8_t buffer[2]; +}; + +/** + * IXM42xxx SPI Transport Layer. + * + * @see Ixm42xxx + * + * @tparam SpiMaster SpiMaster interface + * @tparam Cs Chip-select pin + * + * @ingroup modm_driver_ixm42xxx_transport + * @author Rasmus Kleist Hørlyck Sørensen + */ +template < class SpiMaster, class Cs > +class Ixm42xxxTransportSpi : public modm::SpiDevice< SpiMaster >, protected modm::NestedResumable< 4 > +{ +public: + Ixm42xxxTransportSpi(uint8_t /*address*/); + + /// pings the sensor + modm::ResumableResult + ping(); + +protected: + // RAW REGISTER ACCESS + /// write a 8bit value + modm::ResumableResult + write(uint8_t reg, uint8_t value); + + /// read a 8bit value + modm::ResumableResult + read(uint8_t reg, uint8_t &value); + + /// read multiple 8bit values from a start register + modm::ResumableResult + read(uint8_t reg, uint8_t *buffer, std::size_t length); + +private: + uint8_t whoAmI; + + // write read bit on the address + static constexpr uint8_t Read = Bit7; + static constexpr uint8_t Write = 0x00; +}; + +} // namespace modm + +#include "ixm42xxx_transport_impl.hpp" + +#endif // MODM_IXM42XXX_TRANSPORT_HPP diff --git a/src/modm/driver/inertial/ixm42xxx_transport_impl.hpp b/src/modm/driver/inertial/ixm42xxx_transport_impl.hpp new file mode 100644 index 0000000000..bc3853de5e --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_transport_impl.hpp @@ -0,0 +1,135 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_IXM42XXX_TRANSPORT_HPP +# error "Don't include this file directly, use 'ixm42xxx_transport.hpp' instead!" +#endif + +// ---------------------------------------------------------------------------- + +template < class I2cMaster > +modm::Ixm42xxxTransportI2c::Ixm42xxxTransportI2c(uint8_t address) + : I2cDevice(address) +{ +} + +// ---------------------------------------------------------------------------- + +template < class I2cMaster > +modm::ResumableResult +modm::Ixm42xxxTransportI2c::write(uint8_t reg, uint8_t value) +{ + RF_BEGIN(); + + buffer[0] = reg; + buffer[1] = value; + + this->transaction.configureWrite(buffer, 2); + + RF_END_RETURN_CALL( this->runTransaction() ); +} + +// ---------------------------------------------------------------------------- + +template < class I2cMaster > +modm::ResumableResult +modm::Ixm42xxxTransportI2c::read(uint8_t reg, uint8_t &value) +{ + return read(reg, &value, 1); +} + +// ---------------------------------------------------------------------------- + +template < class I2cMaster > +modm::ResumableResult +modm::Ixm42xxxTransportI2c::read(uint8_t reg, uint8_t *buffer, std::size_t length) +{ + RF_BEGIN(); + + this->buffer[0] = reg; + this->transaction.configureWriteRead(this->buffer, 1, buffer, length); + + RF_END_RETURN_CALL( this->runTransaction() ); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::Ixm42xxxTransportSpi::Ixm42xxxTransportSpi(uint8_t /*address*/) + : whoAmI(0) +{ + Cs::setOutput(modm::Gpio::High); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +modm::Ixm42xxxTransportSpi::ping() +{ + RF_BEGIN(); + + whoAmI = 0; + RF_CALL(read(0x75, whoAmI)); + + RF_END_RETURN(whoAmI != 0); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +modm::Ixm42xxxTransportSpi::write(uint8_t reg, uint8_t value) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + RF_CALL(SpiMaster::transfer(reg | Write)); + RF_CALL(SpiMaster::transfer(value)); + + if (this->releaseMaster()) + Cs::set(); + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +modm::Ixm42xxxTransportSpi::read(uint8_t reg, uint8_t &value) +{ + return read(reg, &value, 1); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +modm::Ixm42xxxTransportSpi::read(uint8_t reg, uint8_t *buffer, std::size_t length) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + RF_CALL(SpiMaster::transfer(reg | Read)); + RF_CALL(SpiMaster::transfer(nullptr, buffer, length)); + + if (this->releaseMaster()) + Cs::set(); + + RF_END_RETURN(true); +} From 79f66ff026511b45114a1d0800acd4670181e80a Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 17 Jul 2023 18:56:23 +0200 Subject: [PATCH 029/159] [example] Adding InvenSense 6-Axis IMU example --- examples/nucleo_g474re/ixm42xxx/main.cpp | 100 ++++++++++++++++++++ examples/nucleo_g474re/ixm42xxx/project.xml | 15 +++ 2 files changed, 115 insertions(+) create mode 100644 examples/nucleo_g474re/ixm42xxx/main.cpp create mode 100644 examples/nucleo_g474re/ixm42xxx/project.xml diff --git a/examples/nucleo_g474re/ixm42xxx/main.cpp b/examples/nucleo_g474re/ixm42xxx/main.cpp new file mode 100644 index 0000000000..823410c239 --- /dev/null +++ b/examples/nucleo_g474re/ixm42xxx/main.cpp @@ -0,0 +1,100 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 + +using SpiMaster = modm::platform::SpiMaster1; +using Mosi = GpioA7; +using Miso = GpioA6; +using Sck = GpioA5; +using Cs = GpioC5; + +class ImuThread : public modm::pt::Protothread, public modm::ixm42xxx +{ + using Transport = modm::Ixm42xxxTransportSpi< SpiMaster, Cs >; + +public: + ImuThread() : imu(data), timer(std::chrono::milliseconds(500)) {} + + bool + run() + { + PT_BEGIN(); + + /// Initialize the IMU and verify that it is connected + PT_CALL(imu.initialize()); + while (not PT_CALL(imu.ping())) + { + MODM_LOG_ERROR << "Cannot ping IXM42xxx" << modm::endl; + PT_WAIT_UNTIL(timer.execute()); + } + + /// Configure data sensors + PT_CALL(imu.updateRegister(Register::GYRO_CONFIG0, GyroFs_t(GyroFs::dps2000) | GyroOdr_t(GyroOdr::kHz1))); + PT_CALL(imu.updateRegister(Register::ACCEL_CONFIG0, AccelFs_t(AccelFs::g16) | AccelOdr_t(AccelOdr::kHz1))); + PT_CALL(imu.updateRegister(Register::PWR_MGMT0, GyroMode_t(GyroMode::LowNoise) | AccelMode_t(AccelMode::LowNoise))); + + while (true) + { + PT_WAIT_UNTIL(timer.execute()); + PT_CALL(imu.readSensorData()); + + data.getTemp(&temp); + data.getAccel(&accel); + data.getGyro(&gyro); + + MODM_LOG_INFO.printf("Temp: %.3f\n", temp); + MODM_LOG_INFO.printf("Accel: (%.3f, %.3f, %.3f)\n", accel.x, accel.y, accel.z); + MODM_LOG_INFO.printf("Gyro: (%.3f, %.3f, %.3f)\n", gyro.x, gyro.y, gyro.z); + } + PT_END(); + } + +private: + float temp; + modm::Vector3f accel; + modm::Vector3f gyro; + + modm::ixm42xxxdata::Data data; + modm::Ixm42xxx< Transport > imu; + + modm::PeriodicTimer timer; + +} imuThread; + +int +main() +{ + Board::initialize(); + + Cs::setOutput(modm::Gpio::High); + SpiMaster::connect(); + SpiMaster::initialize(); + + MODM_LOG_INFO << "==========IXM-42xxx Test==========" << modm::endl; + MODM_LOG_DEBUG << "Debug logging here" << modm::endl; + MODM_LOG_INFO << "Info logging here" << modm::endl; + MODM_LOG_WARNING << "Warning logging here" << modm::endl; + MODM_LOG_ERROR << "Error logging here" << modm::endl; + MODM_LOG_INFO << "==================================" << modm::endl; + + while (true) + { + imuThread.run(); + } + + return 0; +} diff --git a/examples/nucleo_g474re/ixm42xxx/project.xml b/examples/nucleo_g474re/ixm42xxx/project.xml new file mode 100644 index 0000000000..5262c37935 --- /dev/null +++ b/examples/nucleo_g474re/ixm42xxx/project.xml @@ -0,0 +1,15 @@ + + modm:nucleo-g474re + + + + + modm:driver:ixm42xxx + modm:math:geometry + modm:platform:gpio + modm:platform:spi:1 + modm:processing:protothread + modm:processing:timer + modm:build:scons + + \ No newline at end of file From 11a782955af8420410460ea31e45827ba4de7bdc Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 17 Jul 2023 18:57:09 +0200 Subject: [PATCH 030/159] [driver] Adding InvenSense FIFO support --- src/modm/driver/inertial/ixm42xxx.hpp | 33 ++++ src/modm/driver/inertial/ixm42xxx.lb | 1 + src/modm/driver/inertial/ixm42xxx_data.hpp | 162 +++++++++++++++++- .../driver/inertial/ixm42xxx_data_impl.hpp | 136 +++++++++++++++ src/modm/driver/inertial/ixm42xxx_impl.hpp | 71 ++++++++ 5 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 src/modm/driver/inertial/ixm42xxx_data_impl.hpp diff --git a/src/modm/driver/inertial/ixm42xxx.hpp b/src/modm/driver/inertial/ixm42xxx.hpp index daed1c8155..fdbd6c25ed 100644 --- a/src/modm/driver/inertial/ixm42xxx.hpp +++ b/src/modm/driver/inertial/ixm42xxx.hpp @@ -83,6 +83,39 @@ class Ixm42xxx : public ixm42xxx, public Transport modm::ResumableResult readSensorData(); + /** + * @brief Read the FSYNC timestamp from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readFsyncTimestamp(uint16_t *timestamp); + + /** + * @brief Read the FIFO count from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readFifoCount(uint16_t *count); + + /** + * @brief Read the FIFO data from the device + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + readFifoData(); + + /** + * @brief Set the FIFO watermark used to generate FIFO_WM_GT interrupt + * @warning The FIFO watermarkl should be set, before choosing this interrupt source. + * @return False in case of any error, e.g. if some register access is not + * permitted. + */ + modm::ResumableResult + writeFifoWatermark(uint16_t watermark); + public: /** * @brief update a single register. diff --git a/src/modm/driver/inertial/ixm42xxx.lb b/src/modm/driver/inertial/ixm42xxx.lb index a919a2cb17..4246abc773 100644 --- a/src/modm/driver/inertial/ixm42xxx.lb +++ b/src/modm/driver/inertial/ixm42xxx.lb @@ -42,6 +42,7 @@ def build(env): env.copy("ixm42xxx.hpp") env.copy("ixm42xxx_impl.hpp") env.copy("ixm42xxx_data.hpp") + env.copy("ixm42xxx_data_impl.hpp") env.copy("ixm42xxx_definitions.hpp") env.copy("ixm42xxx_transport.hpp") env.copy("ixm42xxx_transport_impl.hpp") diff --git a/src/modm/driver/inertial/ixm42xxx_data.hpp b/src/modm/driver/inertial/ixm42xxx_data.hpp index 08c83b491a..3bcd3003f6 100644 --- a/src/modm/driver/inertial/ixm42xxx_data.hpp +++ b/src/modm/driver/inertial/ixm42xxx_data.hpp @@ -14,6 +14,7 @@ #ifndef MODM_IXM42XXX_DATA_HPP #define MODM_IXM42XXX_DATA_HPP +#include #include @@ -33,7 +34,9 @@ Data template < class Transport > friend class ::modm::Ixm42xxx; - Data() : + Data(std::span fifoBuffer = std::span()) : + fifoCount(0), + fifoBuffer(fifoBuffer), accelScale(16.f), gyroScale(2000.f) { @@ -86,6 +89,18 @@ Data getGyroScale() const { return gyroScale; } + constexpr uint16_t + getFifoCount() const + { return fifoCount; } + + constexpr uint16_t + getFifoBufferSize() const + { return fifoBuffer.size(); } + + constexpr std::span + getFifoData() const + { return fifoBuffer.subspan(0, fifoCount); } + private: struct SensorData { @@ -94,12 +109,157 @@ Data int16_t gyro[3]; } sensorData; + uint16_t fifoCount; + std::span fifoBuffer; + float accelScale; float gyroScale; }; +/// @ingroup modm_driver_ixm42xxx +struct +FifoPacket +{ + FifoPacket() : header(0), accel(0), gyro(0), temp(0), timestamp(0), extension(0) {} + + // DATA ACCESS + ///@{ + int16_t + getTemp() const + { return temp; } + + void + getTemp(int16_t *temp) const + { *temp = getTemp(); } + + void + getTemp(float *temp) const + { float t = getTemp(); *temp = (header & HEADER_20) ? t / 132.48f + 25.f : t / 2.07f + 25.f; } + + Vector3li + getAccel() const; + + void + getAccel(Vector3li *accel) const + { Vector3li a = getAccel(); *accel = a; } + + void + getAccel(Vector3f *accel, float scale = 16.f) const + { Vector3f a = getAccel(); *accel = (header & HEADER_20) ? a / 8192.f : a * scale / INT16_MAX; } + + Vector3li + getGyro() const; + + void + getGyro(Vector3li *gyro) const + { Vector3li g = getGyro(); *gyro = g; } + + void + getGyro(Vector3f *gyro, float scale = 2000.f) const + { Vector3f g = getGyro(); *gyro = (header & HEADER_20) ? g / 131.f : g * scale / INT16_MAX; } + + uint16_t + getTimestamp() const + { return timestamp; } + ///@} + + constexpr bool + containsSensorData() const + { return (header & HEADER_MSG) == 0; } + + constexpr bool + containsAccelData() const + { return header & HEADER_ACCEL; } + + constexpr bool + containsGyroData() const + { return header & HEADER_GYRO; } + + constexpr bool + containsOdrTimestamp() const + { return header & HEADER_TIMESTAMP_ODR; } + + constexpr bool + containsFsyncTimestamp() const + { return header & HEADER_TIMESTAMP_FSYNC; } + + constexpr bool + isExtended() const + { return header & HEADER_20; } + +public: + + static uint16_t + parse(std::span fifoData, FifoPacket &fifoPacket, uint16_t fifoIndex = 0); + +private: + + int header; + int16_t accel[3]; + int16_t gyro[3]; + int16_t temp; + uint16_t timestamp; + int8_t extension[3]; + + enum + { + HEADER_MSG = Bit7, + HEADER_ACCEL = Bit6, + HEADER_GYRO = Bit5, + HEADER_20 = Bit4, + HEADER_TIMESTAMP_ODR = Bit3, + HEADER_TIMESTAMP_FSYNC = Bit2, + HEADER_ODR_ACCEL = Bit1, + HEADER_ODR_GYRO = Bit0, + }; + +public: + constexpr bool operator==(const FifoPacket& rhs) const; +}; + +/// @ingroup modm_driver_ixm42xxx +template +struct +FifoData : public Data +{ + FifoData() : Data(std::span{fifoBuffer}) {} + + struct iterator + { + public: + typedef std::input_iterator_tag iterator_category; + typedef FifoPacket value_type; + typedef FifoPacket& reference; + typedef FifoPacket* pointer; + + constexpr iterator(std::span data, uint16_t index) : fifoData(data), fifoIndex(index) + { + fifoIndex = FifoPacket::parse(fifoData, fifoPacket, fifoIndex); + } + + constexpr iterator operator++() { fifoIndex = FifoPacket::parse(fifoData, fifoPacket, fifoIndex); return *this; } + constexpr reference operator*() { return fifoPacket; } + constexpr pointer operator->() { return &fifoPacket; } + constexpr bool operator==(const iterator& rhs) { return fifoIndex == rhs.fifoIndex && fifoPacket == rhs.fifoPacket; } + + private: + std::span fifoData; + uint16_t fifoIndex; + + FifoPacket fifoPacket; + }; + + constexpr iterator begin() const { return iterator(getFifoData(), 0); } + constexpr iterator end() const { return iterator(getFifoData(), getFifoCount()); } + +private: + uint8_t fifoBuffer[FifoBufferSize]; +}; + } // ixm42xxxdata namespace } // namespace modm +#include "ixm42xxx_data_impl.hpp" + #endif // MODM_IXM42XXX_DATA_HPP \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx_data_impl.hpp b/src/modm/driver/inertial/ixm42xxx_data_impl.hpp new file mode 100644 index 0000000000..38c18b65c7 --- /dev/null +++ b/src/modm/driver/inertial/ixm42xxx_data_impl.hpp @@ -0,0 +1,136 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 + +#ifndef MODM_IXM42XXX_DATA_HPP +# error "Don't include this file directly, use 'ixm42xxx_data.hpp' instead!" +#endif + +namespace modm +{ + +namespace ixm42xxxdata +{ + +Vector3li +FifoPacket::getAccel() const +{ + Vector3li a; + if (header & HEADER_20) + { + a.x = accel[0] << 2 | ((extension[0] & 0xF0) >> 6); + a.y = accel[1] << 2 | ((extension[1] & 0xF0) >> 6); + a.z = accel[2] << 2 | ((extension[2] & 0xF0) >> 6); + } + else + { + a.x = accel[0]; + a.y = accel[1]; + a.z = accel[2]; + } + return a; +} + +Vector3li +FifoPacket::getGyro() const +{ + Vector3li g; + if (header & HEADER_20) + { + g.x = ((gyro[0] << 3) | (extension[0] & 0x0F)); + g.y = ((gyro[1] << 3) | (extension[1] & 0x0F)); + g.z = ((gyro[2] << 3) | (extension[2] & 0x0F)); + } + else + { + g.x = gyro[0]; + g.y = gyro[1]; + g.z = gyro[2]; + } + return g; +} + +uint16_t +FifoPacket::parse(std::span fifoData, FifoPacket &fifoPacket, uint16_t fifoIndex) +{ + if (fifoIndex < fifoData.size() && (fifoData[fifoIndex] & HEADER_MSG) == 0) + { + // Packet contains sensor data + fifoPacket.header = fifoData[fifoIndex++]; + if (fifoPacket.header & HEADER_ACCEL) + { + // Packet is sized so that accel data have location in the packet + std::memcpy(fifoPacket.accel, fifoData.data() + fifoIndex, 6); + fifoIndex += 6; + } + + if (fifoPacket.header & HEADER_GYRO) + { + // Packet is sized so that gyro data have location in the packet + std::memcpy(fifoPacket.gyro, fifoData.data() + fifoIndex, 6); + fifoIndex += 6; + } + + if ((fifoPacket.header & HEADER_ACCEL) || (fifoPacket.header & HEADER_GYRO)) + { + if (fifoPacket.header & HEADER_20) + { + std::memcpy(&fifoPacket.temp, fifoData.data() + fifoIndex, 2); + fifoIndex += 2; + } + else + { + std::memcpy(&fifoPacket.temp, fifoData.data() + fifoIndex, 1); + fifoIndex += 1; + } + } + + if ((fifoPacket.header & HEADER_TIMESTAMP_ODR) || (fifoPacket.header & HEADER_TIMESTAMP_FSYNC)) + { + // Packet contains ODR or FSYNC Timestamp + std::memcpy(&fifoPacket.timestamp, fifoData.data() + fifoIndex, 2); + fifoIndex += 2; + } + + if (fifoPacket.header & HEADER_20) + { + // Packet has a new and valid sample of extended 20-bit data for gyro and/or accel + std::memcpy(fifoPacket.extension, fifoData.data() + fifoIndex, 3); + fifoIndex += 3; + } + } + else + { + // FIFO is empty + fifoPacket = FifoPacket(); + fifoIndex = fifoData.size(); + } + + return fifoIndex; +} + +constexpr bool +FifoPacket::operator==(const FifoPacket& rhs) const +{ + return ((this->header == rhs.header) && + (this->temp == rhs.temp) && + (this->timestamp == rhs.timestamp) && + (std::equal(accel, accel + 3, rhs.accel)) && + (std::equal(gyro, gyro + 3, rhs.gyro)) && + (std::equal(extension, extension + 3, rhs.extension))); +} + +} // ixm42xxxdata namespace + +} // modm namespace \ No newline at end of file diff --git a/src/modm/driver/inertial/ixm42xxx_impl.hpp b/src/modm/driver/inertial/ixm42xxx_impl.hpp index 5b1cccd4a8..574841d362 100644 --- a/src/modm/driver/inertial/ixm42xxx_impl.hpp +++ b/src/modm/driver/inertial/ixm42xxx_impl.hpp @@ -95,6 +95,77 @@ Ixm42xxx< Transport >::readSensorData() // ----------------------------------------------------------------------------- +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readFsyncTimestamp(uint16_t *timestamp) +{ + /// TODO: Verify endianess + return readRegister(Register::TMST_FSYNCH, reinterpret_cast(timestamp), 2); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readFifoCount(uint16_t *count) +{ + RF_BEGIN(); + + /// TODO: Figure out why the endianess seem to be wrong (possibly like FIFO_CONFIG2-3) + /// Read FIFO_COUNTL to latch new data for both FIFO_COUNTH and FIFO_COUNTL + if (RF_CALL(readRegister(Register::FIFO_COUNTL, &readByte))) + { + *count = uint16_t(readByte) << 8; + if (RF_CALL(readRegister(Register::FIFO_COUNTH, &readByte))) + { + *count |= uint16_t(readByte); + RF_RETURN(true); + } + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::readFifoData() +{ + RF_BEGIN(); + + if (RF_CALL(readFifoCount(&data.fifoCount))) + { + data.fifoCount = std::min(data.fifoCount, data.fifoBuffer.size()); + if (data.fifoCount > 0) + { + RF_RETURN_CALL(readRegister(Register::FIFO_DATA, data.fifoBuffer.data(), data.fifoCount)); + } + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport > +modm::ResumableResult +Ixm42xxx< Transport >::writeFifoWatermark(uint16_t watermark) +{ + RF_BEGIN(); + + /// TODO: Determine what endianess to use + if (RF_CALL(writeRegister(Register::FIFO_CONFIG2, watermark & 0xFF)) && + RF_CALL(writeRegister(Register::FIFO_CONFIG3, (watermark >> 8) & 0xFF))) + { + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + template < class Transport > modm::ResumableResult modm::Ixm42xxx< Transport >::updateRegister(Register reg, Register_t setMask, Register_t clearMask) From 8012d824bcef380cddc401a62f0c1a5f0596297a Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 17 Jul 2023 18:57:27 +0200 Subject: [PATCH 031/159] [example] Adding InvenSense FIFO example --- examples/nucleo_g474re/ixm42xxx_fifo/main.cpp | 148 ++++++++++++++++++ .../nucleo_g474re/ixm42xxx_fifo/project.xml | 16 ++ 2 files changed, 164 insertions(+) create mode 100644 examples/nucleo_g474re/ixm42xxx_fifo/main.cpp create mode 100644 examples/nucleo_g474re/ixm42xxx_fifo/project.xml diff --git a/examples/nucleo_g474re/ixm42xxx_fifo/main.cpp b/examples/nucleo_g474re/ixm42xxx_fifo/main.cpp new file mode 100644 index 0000000000..2f9b46bb8d --- /dev/null +++ b/examples/nucleo_g474re/ixm42xxx_fifo/main.cpp @@ -0,0 +1,148 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 + +namespace +{ + volatile bool interrupt = false; +} + +using SpiMaster = modm::platform::SpiMaster1; +using Mosi = GpioA7; +using Miso = GpioA6; +using Sck = GpioA5; +using Cs = GpioC5; + +using Int1 = GpioC3; + +class ImuThread : public modm::pt::Protothread, public modm::ixm42xxx +{ + using Transport = modm::Ixm42xxxTransportSpi< SpiMaster, Cs >; + +public: + ImuThread() : imu(fifoData), timer(std::chrono::milliseconds(500)) {} + + bool + run() + { + PT_BEGIN(); + + Int1::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) + { + interrupt = true; + }); + + /// Initialize the IMU and verify that it is connected + PT_CALL(imu.initialize()); + while (not PT_CALL(imu.ping())) + { + MODM_LOG_ERROR << "Cannot ping IXM-42xxx" << modm::endl; + PT_WAIT_UNTIL(timer.execute()); + } + + MODM_LOG_INFO << "IXM-42xxx Initialized" << modm::endl; + MODM_LOG_INFO << "Fifo Buffer Size: " << fifoData.getFifoBufferSize() << modm::endl; + + /// Configure FIFO + PT_CALL(imu.updateRegister(Register::FIFO_CONFIG, FifoMode_t(FifoMode::StopOnFull))); + PT_CALL(imu.updateRegister(Register::FIFO_CONFIG1, FifoConfig1::FIFO_HIRES_EN | FifoConfig1::FIFO_TEMP_EN | FifoConfig1::FIFO_GYRO_EN | FifoConfig1::FIFO_ACCEL_EN)); + PT_CALL(imu.writeFifoWatermark(1024)); + + /// Configure interrupt + PT_CALL(imu.updateRegister(Register::INT_CONFIG, IntConfig::INT1_MODE | IntConfig::INT1_DRIVE_CIRCUIT | IntConfig::INT1_POLARITY)); + PT_CALL(imu.updateRegister(Register::INT_CONFIG1, IntConfig1::INT_ASYNC_RESET)); + PT_CALL(imu.updateRegister(Register::INT_SOURCE0, IntSource0::FIFO_THS_INT1_EN | IntSource0::FIFO_FULL_INT1_EN, IntSource0::UI_DRDY_INT1_EN)); + + /// Configure data sensors + PT_CALL(imu.updateRegister(Register::GYRO_CONFIG0, GyroFs_t(GyroFs::dps2000) | GyroOdr_t(GyroOdr::kHz1))); + PT_CALL(imu.updateRegister(Register::ACCEL_CONFIG0, AccelFs_t(AccelFs::g16) | AccelOdr_t(AccelOdr::kHz1))); + PT_CALL(imu.updateRegister(Register::PWR_MGMT0, GyroMode_t(GyroMode::LowNoise) | AccelMode_t(AccelMode::LowNoise))); + + while (true) + { + if (interrupt) + { + PT_CALL(imu.readRegister(Register::INT_STATUS, &intStatus.value)); + interrupt = false; + } + + if (intStatus.any(IntStatus::FIFO_FULL_INT | IntStatus::FIFO_THS_INT)) + { + if (PT_CALL(imu.readFifoData())) + { + // Count packets in FIFO buffer and print contents of last packet + uint16_t count = 0; + modm::ixm42xxxdata::FifoPacket fifoPacket; + for (const auto &packet : fifoData) + { + fifoPacket = packet; + count++; + } + + float temp; + modm::Vector3f accel; + modm::Vector3f gyro; + + fifoPacket.getTemp(&temp); + fifoPacket.getAccel(&accel, fifoData.getAccelScale()); + fifoPacket.getGyro(&gyro, fifoData.getGyroScale()); + + MODM_LOG_INFO.printf("Temp: %f\n", temp); + MODM_LOG_INFO.printf("Accel: (%f, %f, %f)\n", accel.x, accel.y, accel.z); + MODM_LOG_INFO.printf("Gyro: (%f, %f, %f)\n", gyro.x, gyro.y, gyro.z); + MODM_LOG_INFO.printf("FIFO packet count: %u\n", count); + } + intStatus.reset(IntStatus::FIFO_FULL_INT | IntStatus::FIFO_THS_INT); + } + } + PT_END(); + } + +private: + + /// Due to the non-deterministic nature of system operation, driver memory allocation should always be the largest size of 2080 bytes. + modm::ixm42xxxdata::FifoData<2080> fifoData; + modm::ixm42xxx::IntStatus_t intStatus; + modm::Ixm42xxx< Transport > imu; + + modm::PeriodicTimer timer; + +} imuThread; + +int +main() +{ + Board::initialize(); + + Cs::setOutput(modm::Gpio::High); + SpiMaster::connect(); + SpiMaster::initialize(); + + MODM_LOG_INFO << "==========IXM-42xxx Test==========" << modm::endl; + MODM_LOG_DEBUG << "Debug logging here" << modm::endl; + MODM_LOG_INFO << "Info logging here" << modm::endl; + MODM_LOG_WARNING << "Warning logging here" << modm::endl; + MODM_LOG_ERROR << "Error logging here" << modm::endl; + MODM_LOG_INFO << "==================================" << modm::endl; + + while (true) + { + imuThread.run(); + } + + return 0; +} diff --git a/examples/nucleo_g474re/ixm42xxx_fifo/project.xml b/examples/nucleo_g474re/ixm42xxx_fifo/project.xml new file mode 100644 index 0000000000..fcf707cd29 --- /dev/null +++ b/examples/nucleo_g474re/ixm42xxx_fifo/project.xml @@ -0,0 +1,16 @@ + + modm:nucleo-g474re + + + + + modm:driver:ixm42xxx + modm:math:geometry + modm:platform:exti + modm:platform:gpio + modm:platform:spi:1 + modm:processing:protothread + modm:processing:timer + modm:build:scons + + \ No newline at end of file From a05cc62ef9567147a330086744806760cbf15195 Mon Sep 17 00:00:00 2001 From: Klaus Schnass Date: Thu, 15 Jun 2023 16:23:27 +0200 Subject: [PATCH 032/159] shared IRQ handling for fdcan on STM32G0 --- README.md | 2 +- src/modm/platform/can/stm32-fdcan/can.cpp.in | 24 ++++++++++++-- src/modm/platform/can/stm32-fdcan/can.hpp.in | 15 ++++++++- .../can/stm32-fdcan/can_shared_irqs.cpp.in | 28 ++++++++++++++++ src/modm/platform/can/stm32-fdcan/module.lb | 33 ++++++++++++++++--- 5 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 src/modm/platform/can/stm32-fdcan/can_shared_irqs.cpp.in diff --git a/README.md b/README.md index 579f3d93d7..1373f7e4fb 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -○ +✅ ✅ ○ ✕ diff --git a/src/modm/platform/can/stm32-fdcan/can.cpp.in b/src/modm/platform/can/stm32-fdcan/can.cpp.in index 5fe3c7930a..a45db93930 100644 --- a/src/modm/platform/can/stm32-fdcan/can.cpp.in +++ b/src/modm/platform/can/stm32-fdcan/can.cpp.in @@ -212,7 +212,12 @@ modm::platform::Fdcan{{ id }}::initializeWithPrescaler( // line 0 used as TX and error interrupt // generated on finished frame transmission and error state +%% if shared_irq_it0 +void +modm::platform::Fdcan{{ id }}::irqIT0() +%% else MODM_ISR({{ reg }}_IT0) +%% endif { %% if options["buffer.tx"] > 0 if ({{ reg }}->IR & FDCAN_IR_TC) { @@ -239,7 +244,12 @@ MODM_ISR({{ reg }}_IT0) // line 1 used as RX interrupt // generated on received frame +%% if shared_irq_it1 +void +modm::platform::Fdcan{{ id }}::irqIT1() +%% else MODM_ISR({{ reg }}_IT1) +%% endif { %% if options["buffer.rx"] > 0 int_fast16_t msgRetrieveLimit = rxQueue.getMaxSize() - rxQueue.getSize(); @@ -315,11 +325,21 @@ modm::platform::Fdcan{{ id }}::configureMode(Mode mode) void modm::platform::Fdcan{{ id }}::configureInterrupts(uint32_t interruptPriority) { +%% if shared_irq_it0 + NVIC_SetPriority({{ shared_irq_it0 }}_IRQn, interruptPriority); + NVIC_EnableIRQ({{ shared_irq_it0 }}_IRQn); +%% else NVIC_SetPriority({{ reg }}_IT0_IRQn, interruptPriority); - NVIC_SetPriority({{ reg }}_IT1_IRQn, interruptPriority); - NVIC_EnableIRQ({{ reg }}_IT0_IRQn); +%% endif + +%% if shared_irq_it1 + NVIC_SetPriority({{ shared_irq_it1 }}_IRQn, interruptPriority); + NVIC_EnableIRQ({{ shared_irq_it1 }}_IRQn); +%% else + NVIC_SetPriority({{ reg }}_IT1_IRQn, interruptPriority); NVIC_EnableIRQ({{ reg }}_IT1_IRQn); +%% endif // enable both interrupts lines (0 and 1) {{ reg }}->ILE = FDCAN_ILE_EINT1 | FDCAN_ILE_EINT0; diff --git a/src/modm/platform/can/stm32-fdcan/can.hpp.in b/src/modm/platform/can/stm32-fdcan/can.hpp.in index cece052457..e185516353 100644 --- a/src/modm/platform/can/stm32-fdcan/can.hpp.in +++ b/src/modm/platform/can/stm32-fdcan/can.hpp.in @@ -358,8 +358,21 @@ private: EnterInitMode(const EnterInitMode&) = delete; EnterInitMode& operator=(const EnterInitMode&) = delete; }; -}; +%% if shared_irq_it0 + friend void ::{{ shared_irq_it0 }}_IRQHandler(); + + static void + irqIT0(); +%% endif + +%% if shared_irq_it0 + friend void ::{{ shared_irq_it1 }}_IRQHandler(); + + static void + irqIT1(); +%% endif +}; } // namespace modm::platform #endif // MODM_STM32_FDCAN{{ id }}_HPP diff --git a/src/modm/platform/can/stm32-fdcan/can_shared_irqs.cpp.in b/src/modm/platform/can/stm32-fdcan/can_shared_irqs.cpp.in new file mode 100644 index 0000000000..3187823f64 --- /dev/null +++ b/src/modm/platform/can/stm32-fdcan/can_shared_irqs.cpp.in @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023, Klaus Schnass (Zuehlke Engineering) + * + * 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 + +%% for instances in shared_irqs.values() +%% for instance in instances | sort +#include "can_{{ instance[0] }}.hpp" +%% endfor +%% endfor + +%% for irq,instances in shared_irqs.items() +MODM_ISR({{ irq }}) +{ +%% for id,line in instances | sort + modm::platform::Fdcan{{ id }}::irq{{ line }}(); +%% endfor +} +%% endfor diff --git a/src/modm/platform/can/stm32-fdcan/module.lb b/src/modm/platform/can/stm32-fdcan/module.lb index 6bcc460042..548aa40567 100644 --- a/src/modm/platform/can/stm32-fdcan/module.lb +++ b/src/modm/platform/can/stm32-fdcan/module.lb @@ -12,6 +12,10 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # ----------------------------------------------------------------------------- +from collections import defaultdict + +global_properties = {} + def load_options(module): module.add_option( NumericOption( @@ -26,6 +30,13 @@ def load_options(module): minimum=1, maximum="64Ki-2", default=32)) +def get_shared_irq(device, irq): + irqs = [v["name"] for v in device.get_driver("core")["vector"]] + irqs = [v for v in irqs if irq in v] + assert(len(irqs) == 1) + + return irqs[0] + class Instance(Module): def __init__(self, instance): self.instance = instance @@ -39,6 +50,7 @@ class Instance(Module): return True def build(self, env): + global global_properties device = env[":target"] driver = device.get_driver("fdcan") @@ -47,6 +59,16 @@ class Instance(Module): properties["driver"] = driver properties["id"] = self.instance properties["reg"] = 'FDCAN{}'.format(self.instance) + properties["shared_irq_it0"] = False + properties["shared_irq_it1"] = False + + if device.identifier.family == "g0": + shared_irq_it0 = get_shared_irq(device, "FDCAN_IT0") + properties["shared_irq_it0"] = shared_irq_it0 + global_properties["shared_irqs"][shared_irq_it0].append([self.instance, "IT0"]) + shared_irq_it1 = get_shared_irq(device, "FDCAN_IT1") + properties["shared_irq_it1"] = shared_irq_it1 + global_properties["shared_irqs"][shared_irq_it1].append([self.instance, "IT1"]) env.substitutions = properties env.outbasepath = "modm/src/modm/platform/can" @@ -64,11 +86,6 @@ def prepare(module, options): if not device.has_driver("fdcan:stm32"): return False - # STM32G0 devices are currently unsupported due to shared interrupt handler - # TODO: fix driver - if device.identifier.family == "g0": - return False - module.depends( ":architecture:assert", ":architecture:atomic", @@ -83,6 +100,8 @@ def prepare(module, options): ":platform:rcc", ":utils") + global_properties["shared_irqs"] = defaultdict(list) + driver = device.get_driver("fdcan") for instance in listify(driver["instance"]): @@ -91,5 +110,9 @@ def prepare(module, options): return True def build(env): + env.substitutions.update(global_properties) env.outbasepath = "modm/src/modm/platform/can" + env.copy("message_ram.hpp") + if len(global_properties["shared_irqs"]) > 0: + env.template("can_shared_irqs.cpp.in") From 5d03d53fad3fb08ce4e2be35a5cb60d5de86bc82 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 26 Jul 2023 16:40:15 +0200 Subject: [PATCH 033/159] [stm32] Fix STM32G0 ADC The internal voltage regulator is not enabled and calibration never suceeds. --- src/modm/platform/adc/stm32f0/adc_impl.hpp.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modm/platform/adc/stm32f0/adc_impl.hpp.in b/src/modm/platform/adc/stm32f0/adc_impl.hpp.in index 91b6e19a88..09c503cf5a 100644 --- a/src/modm/platform/adc/stm32f0/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f0/adc_impl.hpp.in @@ -28,6 +28,11 @@ modm::platform::Adc{{ id }}::initialize() { Rcc::enable(); +%% if target.family in ["g0"] + ADC1->CR |= ADC_CR_ADVREGEN; + modm::delay_us(20); +%% endif + if constexpr (mode == ClockMode::Synchronous) { constexpr auto result = Prescaler::from_power( SystemClock::Apb, frequency, {{2 if target.family in ["f0"] else 1}}, 4); From fc85e139c0586a45f202b1b54c1b2c1efd4123a8 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 11 Jul 2023 17:20:05 +0200 Subject: [PATCH 034/159] [stm32] Add support for H7 16-bit and 12-bit ADCs in stm32-f3 driver --- README.md | 2 +- src/modm/platform/adc/stm32f3/adc.hpp.in | 94 ++++++++++++++----- src/modm/platform/adc/stm32f3/adc_impl.hpp.in | 57 +++++++++-- src/modm/platform/adc/stm32f3/module.lb | 61 +++++++++++- 4 files changed, 177 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 1373f7e4fb..ec66e03826 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -○ +✅ ○ ✅ ✅ diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index 9d80c8be5e..610238c983 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -2,7 +2,7 @@ * Copyright (c) 2013-2014, Kevin Läufer * Copyright (c) 2014, 2016-2018, Niklas Hauser * Copyright (c) 2017, Sascha Schade - * Copyright (c) 2022, Christopher Durand + * Copyright (c) 2022-2023, Christopher Durand * * This file is part of the modm project. * @@ -83,6 +83,21 @@ public: %% elif target["family"] in ["g4"] Opamp2 = 16, Opamp3 = 18, +%% elif target["family"] in ["h7"] +%% if target["name"] in ["a3", "b0", "b3"] + BatDiv4 = 14, + Dac2Out1 = 15, + VSense = 18, + InternalReference = 19, +%% endif + Dac1Out1 = 16, + Dac1Out2 = 17, +%% if target["name"][0] in ["2", "3"] + // BatDiv4 can be connected to channel 16 by setting ADC2_ROUT0 in SYSCFG_ADC2ALT + BatDiv4 = 16, + // The internal reference can be connected to channel 17 by setting ADC2_ROUT1 in SYSCFG_ADC2ALT + InternalReference = 17, +%% endif %% endif %% elif id == 3 %% if target["family"] in ["f3"] @@ -103,6 +118,16 @@ public: BatDiv3 = 17, %% endif InternalReference = 18, +%% elif target["family"] in ["h7"] +%% if resolution == 16 + BatDiv4 = 17, + VSense = 18, + InternalReference = 19, +%% else + BatDiv4 = 16, + VSense = 17, + InternalReference = 18, +%% endif %% endif %% elif id == 4 %% if target["family"] in ["f3"] @@ -175,7 +200,7 @@ public: Div128 = RCC_CFGR2_{{ adc_pre }}_DIV128, Div256 = RCC_CFGR2_{{ adc_pre }}_DIV256, Div256AllBits = RCC_CFGR2_{{ adc_pre }}, // for bit clear -%% elif target["family"] in ["l4", "l5", "g4"] +%% elif target["family"] in ["l4", "l5", "g4", "h7"] Div1 = 0, Div2 = ADC_CCR_PRESC_0, Div4 = ADC_CCR_PRESC_1, @@ -192,29 +217,41 @@ public: %% endif }; -%% if target["family"] in ["l4", "l5", "g4"] +%% if resolution == 16 + enum class SampleTime : uint8_t // TODO: What is the best type? + { + Cycles2 = 0b000, //< 1.5 ADC clock cycles + Cycles3 = 0b001, //< 2.5 ADC clock cycles + Cycles9 = 0b010, //< 8.5 ADC clock cycles + Cycles17 = 0b011, //< 16.5 ADC clock cycles + Cycles33 = 0b100, //< 32.5 ADC clock cycles + Cycles65 = 0b101, //< 64.5 ADC clock cycles + Cycles388 = 0b110, //< 387.5 ADC clock cycles + Cycles811 = 0b111, //< 810.5 ADC clock cycles + }; +%% elif target["family"] in ["l4", "l5", "g4", "h7"] enum class SampleTime : uint8_t // TODO: What is the best type? { - Cycles2 = 0b000, //! 1.5 ADC clock cycles - Cycles7 = 0b001, //! 6.5 ADC clock cycles - Cycles13 = 0b010, //! 12.5 ADC clock cycles - Cycles25 = 0b011, //! 24.5 ADC clock cycles - Cycles48 = 0b100, //! 47.5 ADC clock cycles - Cycles93 = 0b101, //! 92.5 ADC clock cycles - Cycles248 = 0b110, //! 247.5 ADC clock cycles - Cycles641 = 0b111, //! 640.5 ADC clock cycles + Cycles2 = 0b000, //< 1.5 ADC clock cycles + Cycles7 = 0b001, //< 6.5 ADC clock cycles + Cycles13 = 0b010, //< 12.5 ADC clock cycles + Cycles25 = 0b011, //< 24.5 ADC clock cycles + Cycles48 = 0b100, //< 47.5 ADC clock cycles + Cycles93 = 0b101, //< 92.5 ADC clock cycles + Cycles248 = 0b110, //< 247.5 ADC clock cycles + Cycles641 = 0b111, //< 640.5 ADC clock cycles }; %% else enum class SampleTime : uint8_t // TODO: What is the best type? { - Cycles2 = 0b000, //! 1.5 ADC clock cycles - Cycles3 = 0b001, //! 2.5 ADC clock cycles - Cycles5 = 0b010, //! 4.5 ADC clock cycles - Cycles8 = 0b011, //! 7.5 ADC clock cycles - Cycles20 = 0b100, //! 19.5 ADC clock cycles - Cycles62 = 0b101, //! 61.5 ADC clock cycles - Cycles182 = 0b110, //! 181.5 ADC clock cycles - Cycles602 = 0b111, //! 601.5 ADC clock cycles + Cycles2 = 0b000, //< 1.5 ADC clock cycles + Cycles3 = 0b001, //< 2.5 ADC clock cycles + Cycles5 = 0b010, //< 4.5 ADC clock cycles + Cycles8 = 0b011, //< 7.5 ADC clock cycles + Cycles20 = 0b100, //< 19.5 ADC clock cycles + Cycles62 = 0b101, //< 61.5 ADC clock cycles + Cycles182 = 0b110, //< 181.5 ADC clock cycles + Cycles602 = 0b111, //< 601.5 ADC clock cycles }; %% endif enum class CalibrationMode : uint32_t @@ -226,7 +263,7 @@ public: enum class VoltageRegulatorState : uint32_t { -%% if target["family"] in ["l4", "l5", "g4"] +%% if target["family"] in ["l4", "l5", "g4", "h7"] Enabled = ADC_CR_ADVREGEN, %% elif target["family"] in ["f3"] // Intermediate state is needed to move from enabled to disabled @@ -331,6 +368,7 @@ public: static inline void calibrate(const CalibrationMode mode, const bool blocking = true); +%% if resolution < 16 /** * Change the presentation of the ADC conversion result. * @@ -343,6 +381,7 @@ public: */ static inline void setLeftAdjustResult(const bool enable); +%% endif /** * Analog channel selection. @@ -373,11 +412,20 @@ public: return setChannel(getPinChannel(), sampleTime); } /// Get the channel for a Pin +%% if target["family"] in ["h7"] + /// \warning Always returns the positive single-ended channel. + /// Differential mode is not supported yet. +%% endif template< class Gpio > static inline constexpr Channel getPinChannel() { +%% if target["family"] in ["h7"] + using modm::platform::detail::AdcPolarity; + constexpr int8_t channel{detail::AdcChannel}; +%% else constexpr int8_t channel{detail::AdcChannel}; +%% endif static_assert(channel >= 0, "Adc{{id}} does not have a channel for this pin!"); return Channel(channel); } @@ -404,14 +452,14 @@ public: * TODO: is there any limitation to when is can be called?? */ static inline void - startConversion(void); + startConversion(); /** * @return If the conversion is finished. * @pre A conversion should have been stared with startConversion() */ static inline bool - isConversionFinished(void); + isConversionFinished(); /** * @return The most recent 16bit result of the ADC conversion. @@ -426,7 +474,7 @@ public: @endcode */ static inline uint16_t - getValue(void) + getValue() { return ADC{{ id }}->DR; } diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index b731b3131b..e854b823db 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -2,6 +2,7 @@ * Copyright (c) 2013-2014, Kevin Läufer * Copyright (c) 2014, Sascha Schade * Copyright (c) 2014, 2016-2017, Niklas Hauser + * Copyright (c) 2023, Christopher Durand * * This file is part of the modm project. * @@ -29,7 +30,7 @@ modm::platform::Adc{{ id }}::initialize(const ClockMode clk, uint32_t tmp = 0; // enable clock -%% if target["family"] in ["f3", "g4", "l5"] +%% if target["family"] in ["f3", "g4", "l5", "h7"] RCC->{{ ahb }}ENR |= RCC_{{ ahb }}ENR_ADC{{ id_common }}EN; %% elif target["family"] in ["l4"] Rcc::enable(); @@ -40,7 +41,7 @@ modm::platform::Adc{{ id }}::initialize(const ClockMode clk, RCC->{{ ccipr }} |= static_cast(clk_src); %% endif -%% if target["family"] in ["l4", "l5", "g4"] +%% if target["family"] in ["l4", "l5", "g4", "h7"] // Disable deep power down ADC{{ id }}->CR &= ~ADC_CR_DEEPPWD; %% endif @@ -60,11 +61,22 @@ modm::platform::Adc{{ id }}::initialize(const ClockMode clk, ADC{{ id_common_u }}->CCR = tmp; } +%% if resolution == 16 + // Always set boost mode for maximum clock for now. + // This will increase the ADC power consumption on lower ADC clocks. + // TODO: compute ADC input clock and choose correct setting + ADC{{id}}->CR |= ADC_CR_BOOST_Msk; +%% endif + // enable regulator ADC{{ id }}->CR &= ~ADC_CR_ADVREGEN; ADC{{ id }}->CR |= static_cast(VoltageRegulatorState::Enabled); modm::delay_us(10); // FIXME: this is ugly -> find better solution +%% if resolution == 16 + while(!(ADC{{ id }}->ISR & ADC_ISR_LDORDY)); +%% endif + acknowledgeInterruptFlags(InterruptFlag::Ready); calibrate(cal, true); // blocking calibration @@ -90,7 +102,7 @@ modm::platform::Adc{{ id }}::disable(const bool blocking) while(ADC{{ id }}->CR & ADC_CR_ADDIS); } // disable clock -%% if target["family"] in ["f3", "g4", "l5"] +%% if target["family"] in ["f3", "g4", "l5", "h7"] RCC->{{ ahb }}ENR &= ~RCC_{{ ahb }}ENR_ADC{{ id_common }}EN; %% elif target["family"] in ["l4"] Rcc::disable(); @@ -106,7 +118,7 @@ modm::platform::Adc{{ id }}::setPrescaler(const Prescaler pre) tmp &= ~static_cast(Prescaler::Div256AllBits); tmp |= static_cast(pre); RCC->CFGR2 = tmp; -%% elif target["family"] in ["l4", "l5", "g4"] +%% elif target["family"] in ["l4", "l5", "g4", "h7"] tmp = ADC{{ id_common_u }}->CCR; tmp &= ~static_cast(Prescaler::Div256AllBits); tmp |= static_cast(pre); @@ -125,7 +137,11 @@ modm::platform::Adc{{ id }}::calibrate(const CalibrationMode mode, const bool blocking) { if (mode != CalibrationMode::DoNotCalibrate) { +%% if resolution == 16 + ADC{{ id }}->CR |= ADC_CR_ADCAL | ADC_CR_ADCALLIN | +%% else ADC{{ id }}->CR |= ADC_CR_ADCAL | +%% endif static_cast(mode); if(blocking) { // wait for ADC_CR_ADCAL to be cleared by hw @@ -134,24 +150,47 @@ modm::platform::Adc{{ id }}::calibrate(const CalibrationMode mode, } } +%% if resolution < 16 void modm::platform::Adc{{ id }}::setLeftAdjustResult(const bool enable) { if (enable) { +%% if target["family"] in ["h7"] + ADC{{ id }}->CFGR |= ADC3_CFGR_ALIGN; +%% else ADC{{ id }}->CFGR |= ADC_CFGR_ALIGN; - } - else { +%% endif + } else { +%% if target["family"] in ["h7"] + ADC{{ id }}->CFGR &= ~ADC3_CFGR_ALIGN; +%% else ADC{{ id }}->CFGR &= ~ADC_CFGR_ALIGN; +%% endif } } +%% endif bool modm::platform::Adc{{ id }}::setChannel( const Channel channel, const SampleTime sampleTime) { - if(static_cast(channel) > 18) { +%% if target["family"] in ["h7"] and resolution == 16: + if (static_cast(channel) > 19) { + return false; + } +%% else + if (static_cast(channel) > 18) { return false; } +%% endif + +%% if resolution == 16 +%% if target["family"] in ["h7"] and target["name"][0] in ["2", "3"] + ADC{{ id }}->PCSEL_RES0 = (1u << static_cast(channel)); +%% else + ADC{{ id }}->PCSEL = (1u << static_cast(channel)); +%% endif +%% endif uint32_t tmpreg; // SQR1[10:6] contain SQ1[4:0] @@ -206,7 +245,9 @@ void modm::platform::Adc{{ id }}::enableInterruptVector(const uint32_t priority, const bool enable) { -%% if id <= 2 +%% if id <= 2 and target["family"] in ["h7"] + const IRQn_Type INTERRUPT_VECTOR = ADC_IRQn; +%% elif id <= 2 and target["family"] not in ["h7"] const IRQn_Type INTERRUPT_VECTOR = ADC1_2_IRQn; %% elif id <= 5 const IRQn_Type INTERRUPT_VECTOR = ADC{{ id }}_IRQn; diff --git a/src/modm/platform/adc/stm32f3/module.lb b/src/modm/platform/adc/stm32f3/module.lb index cda75902de..ca8fc074bc 100644 --- a/src/modm/platform/adc/stm32f3/module.lb +++ b/src/modm/platform/adc/stm32f3/module.lb @@ -3,7 +3,7 @@ # # Copyright (c) 2016-2018, Niklas Hauser # Copyright (c) 2017, Fabian Greif -# Copyright (c) 2022, Christopher Durand +# Copyright (c) 2022-2023, Christopher Durand # # This file is part of the modm project. # @@ -33,6 +33,14 @@ class Instance(Module): instance_id = int(self.instance) properties["id"] = instance_id + if target["family"] == "h7": + if (instance_id != 3) or (target["name"][0] in ["4", "5"]): + properties["resolution"] = 16 + else: + properties["resolution"] = 12 + else: + properties["resolution"] = 12 + if instance_id == 1: if target["family"] == "f3": # 13-14 reserved @@ -48,6 +56,8 @@ class Instance(Module): # Channel 18: VRefint channels = [1,2,3,4,5,6,7,8,9,10,11,12,14,15] assert(len(channels) == 14) + elif target["family"] == "h7": + channels = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19] else: # 11-14 reserved channels = [1,2,3,4,5,6,7,8,9,10,15,16,17,18] @@ -64,6 +74,19 @@ class Instance(Module): elif target["family"] == "l5": # ADC1 is connected to 16 external channels + 2 internal channels channels = range(1,17) + elif target["family"] == "h7": + if target["name"] in ["a3", "b0", "b3"]: + # Channel 14: VBAT/4 + # Channel 15: DAC2 OUT1 + # Channel 16: DAC1 OUT1 + # Channel 17: DAC1 OUT2 + # Channel 18: VSENSE + # Channel 19: VREFINT + channels = [0,1,2,3,4,5,6,7,8,9,10,11,12,13] + else: + # Channel 16: DAC1 OUT1 + # Channel 17: DAC1 OUT2 + channels = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,18,19] else: # ADC2 is connected to 16 external channels + 2 internal channels channels = range(1,17) @@ -77,6 +100,17 @@ class Instance(Module): # Channel 18: VRefint channels = [1,2,3,4,5,6,7,8,9,10,11,12,14,15,16] assert(len(channels) == 15) + elif target["family"] == "h7": + if properties["resolution"] == 16: + # Channel 17: VBAT/4 + # Channel 18: VSENSE + # Channel 19: VREFINT + channels = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] + else: + # Channel 16: VBAT/4 + # Channel 17: VSENSE + # Channel 18: VREFINT + channels = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] else: # ADC3 is connected to 12 external channels + 4 internal channels channels = [1,2,3,4,6,7,8,9,10,11,12,13] @@ -153,6 +187,21 @@ class Instance(Module): properties["id_common"] = "345" properties["id_common_u"] = "345_COMMON" properties["clock_mux"] = True + elif target["family"] == "h7": + properties["adc_ccr"] = "ADC_CCR" + if target["name"] in ["a3", "b0", "b3"]: + properties["ccipr"] = "SRDCCIPR" + else: + properties["ccipr"] = "D3CCIPR" + if instance_id in [1, 2]: + properties["ahb"] = "AHB1" + properties["id_common"] = "12" + properties["id_common_u"] = "12_COMMON" + else: + properties["ahb"] = "AHB4" + properties["id_common"] = "3" + properties["id_common_u"] = "3_COMMON" + properties["clock_mux"] = True else: raise NotImplementedError @@ -163,8 +212,6 @@ class Instance(Module): properties["shared_irq_ids"] = props["shared_irq_ids"] props["instances"].append(self.instance) - properties["resolution"] = 12 - env.template("adc.hpp.in", "adc_{}.hpp".format(self.instance)) env.template("adc_impl.hpp.in", "adc_{}_impl.hpp".format(self.instance)) env.template("adc_interrupt.hpp.in", "adc_interrupt_{}.hpp".format(self.instance)) @@ -177,7 +224,7 @@ def init(module): def prepare(module, options): device = options[":target"] - if not device.has_driver("adc:stm32-f3"): + if not device.has_driver("adc:stm32-f3") and not device.has_driver("adc:stm32-h7"): return False module.depends( @@ -199,10 +246,14 @@ def prepare(module, options): props["shared_irq_ids"] = [] for irq in shared_irqs: parts = irq[3:].split("_") - shared_irqs_ids = (int(parts[0]), int(parts[1]) ) + shared_irqs_ids = (int(parts[0]), int(parts[1])) props["shared_irqs"][irq] = shared_irqs_ids props["shared_irq_ids"].extend(shared_irqs_ids) + if device.identifier.family == "h7": + props["shared_irqs"]["ADC"] = (1, 2) + props["shared_irq_ids"].extend((1, 2)) + for instance in listify(device.get_driver("adc")["instance"]): module.add_submodule(Instance(int(instance))) From 478461ccc49306d58e4f8c7464a4e0e7e13b5654 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 11 Jul 2023 18:27:03 +0200 Subject: [PATCH 035/159] [example] Add simple ADC example for Nucleo H723ZG --- examples/nucleo_h723zg/adc_simple/main.cpp | 54 +++++++++++++++++++ examples/nucleo_h723zg/adc_simple/project.xml | 10 ++++ 2 files changed, 64 insertions(+) create mode 100644 examples/nucleo_h723zg/adc_simple/main.cpp create mode 100644 examples/nucleo_h723zg/adc_simple/project.xml diff --git a/examples/nucleo_h723zg/adc_simple/main.cpp b/examples/nucleo_h723zg/adc_simple/main.cpp new file mode 100644 index 0000000000..b23dfa2016 --- /dev/null +++ b/examples/nucleo_h723zg/adc_simple/main.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 + +using namespace Board; + +// ADC1 (16 bit) channel 15 +using A0 = GpioA3; +// ADC3 (12 bit) channel 10 +using A1 = GpioC0; + +int main() +{ + Board::initialize(); + + Adc1::connect(); + Adc1::initialize(Adc1::ClockMode::SynchronousPrescaler4, + Adc1::ClockSource::NoClock, + Adc1::Prescaler::Disabled, + Adc1::CalibrationMode::SingleEndedInputsMode); + + Adc3::connect(); + Adc3::initialize(Adc3::ClockMode::SynchronousPrescaler4, + Adc3::ClockSource::NoClock, + Adc3::Prescaler::Disabled, + Adc3::CalibrationMode::SingleEndedInputsMode); + + MODM_LOG_INFO << "ADC Test\n"; + + while (true) { + Adc1::setPinChannel(Adc1::SampleTime::Cycles17); + Adc1::startConversion(); + while (!Adc1::isConversionFinished()); + MODM_LOG_INFO << "ADC1 CH15: " << Adc1::getValue() << '\n'; + + Adc3::setPinChannel(Adc3::SampleTime::Cycles13); + Adc3::startConversion(); + while (!Adc3::isConversionFinished()); + MODM_LOG_INFO << "ADC3 CH10: " << Adc3::getValue() << '\n'; + + modm::delay_ms(500); + } + + return 0; +} diff --git a/examples/nucleo_h723zg/adc_simple/project.xml b/examples/nucleo_h723zg/adc_simple/project.xml new file mode 100644 index 0000000000..f74ce06da3 --- /dev/null +++ b/examples/nucleo_h723zg/adc_simple/project.xml @@ -0,0 +1,10 @@ + + modm:nucleo-h723zg + + + + + modm:build:scons + modm:platform:adc:* + + From 384dd9c518aca6c01a7fbff55fd24bb140467aa4 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 12 Jul 2023 12:33:32 +0200 Subject: [PATCH 036/159] [stm32] Add support for injected conversions in stm32-f3 ADC driver --- src/modm/platform/adc/stm32f3/adc.hpp.in | 70 ++++++++++++++- src/modm/platform/adc/stm32f3/adc_impl.hpp.in | 89 ++++++++++++++++--- 2 files changed, 143 insertions(+), 16 deletions(-) diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index 610238c983..947d7f5238 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -456,14 +456,51 @@ public: /** * @return If the conversion is finished. - * @pre A conversion should have been stared with startConversion() + * @pre A conversion should have been started with startConversion() */ static inline bool isConversionFinished(); /** - * @return The most recent 16bit result of the ADC conversion. - * @pre A conversion should have been stared with startConversion() + * Start a new injected conversion sequence. + * + * @pre Channels must be selected with setInjectedConversionChannel(). + */ + static inline void + startInjectedConversionSequence(); + + /** + * @arg index Index of injected conversion in sequence (0..3) + * @return true if configuration is successful, false if arguments are invalid + */ + static inline bool + setInjectedConversionChannel(uint8_t index, Channel channel, SampleTime sampleTime); + + /** + * @arg index Index of injected conversion in sequence (0..3) + * @return true if configuration is successful, false if arguments are invalid + */ + template + static inline bool + setInjectedConversionChannel(uint8_t index, SampleTime sampleTime); + + /** + * @arg length Length of injected conversion sequence (1..4) + * @return true if configuration is successful, false if arguments are invalid + */ + static inline bool + setInjectedConversionSequenceLength(uint8_t length); + + /** + * @return If the injected conversion sequence is finished. + * @pre An injected conversion should have been started with startInjectedConversionSequence() + */ + static inline bool + isInjectedConversionFinished(); + + /** + * @return The most recent result of the ADC conversion. + * @pre A conversion should have been started with startConversion() * * To have a blocking GET you might do it this way: * @code @@ -473,12 +510,33 @@ public: } @endcode */ - static inline uint16_t + static inline auto getValue() { return ADC{{ id }}->DR; } + /** + * Get result of injected conversion. + * @pre The injected conversion sequence is completed. + */ + static inline auto + getInjectedConversionValue(uint8_t index) + { + switch (index) { + case 0: + return ADC{{ id }}->JDR1; + case 1: + return ADC{{ id }}->JDR2; + case 2: + return ADC{{ id }}->JDR3; + case 3: + [[fallthrough]]; + default: + return ADC{{ id }}->JDR4; + } + } + static inline void enableInterruptVector(const uint32_t priority, const bool enable = true); @@ -493,6 +551,10 @@ public: static inline void acknowledgeInterruptFlags(const InterruptFlag_t flags); + +private: + static inline bool + configureChannel(Channel channel, SampleTime sampleTime); }; } diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index e854b823db..45a3720009 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -62,7 +62,7 @@ modm::platform::Adc{{ id }}::initialize(const ClockMode clk, } %% if resolution == 16 - // Always set boost mode for maximum clock for now. + // Always set highest boost mode for maximum clock for now. // This will increase the ADC power consumption on lower ADC clocks. // TODO: compute ADC input clock and choose correct setting ADC{{id}}->CR |= ADC_CR_BOOST_Msk; @@ -171,8 +171,8 @@ modm::platform::Adc{{ id }}::setLeftAdjustResult(const bool enable) %% endif bool -modm::platform::Adc{{ id }}::setChannel( const Channel channel, - const SampleTime sampleTime) +modm::platform::Adc{{ id }}::configureChannel(Channel channel, + SampleTime sampleTime) { %% if target["family"] in ["h7"] and resolution == 16: if (static_cast(channel) > 19) { @@ -186,16 +186,13 @@ modm::platform::Adc{{ id }}::setChannel( const Channel channel, %% if resolution == 16 %% if target["family"] in ["h7"] and target["name"][0] in ["2", "3"] - ADC{{ id }}->PCSEL_RES0 = (1u << static_cast(channel)); + ADC{{ id }}->PCSEL_RES0 |= (1u << static_cast(channel)); %% else - ADC{{ id }}->PCSEL = (1u << static_cast(channel)); + ADC{{ id }}->PCSEL |= (1u << static_cast(channel)); %% endif %% endif - uint32_t tmpreg; - // SQR1[10:6] contain SQ1[4:0] - ADC{{ id }}->SQR1 = (static_cast(channel) & 0b11111) << 6; - + uint32_t tmpreg = 0; if (static_cast(channel) < 10) { tmpreg = ADC{{ id }}->SMPR1 & ((~ADC_SMPR1_SMP0) << (static_cast(channel) * 3)); @@ -211,6 +208,20 @@ modm::platform::Adc{{ id }}::setChannel( const Channel channel, ADC{{ id }}->SMPR2 = tmpreg; } return true; + +} + + +bool +modm::platform::Adc{{ id }}::setChannel(Channel channel, + SampleTime sampleTime) +{ + if (!configureChannel(channel, sampleTime)) { + return false; + } + + ADC{{ id }}->SQR1 = (static_cast(channel) << ADC_SQR1_SQ1_Pos) & ADC_SQR1_SQ1_Msk; + return true; } void @@ -223,9 +234,8 @@ modm::platform::Adc{{ id }}::setFreeRunningMode(const bool enable) } } - void -modm::platform::Adc{{ id }}::startConversion(void) +modm::platform::Adc{{ id }}::startConversion() { // TODO: maybe add more interrupt flags acknowledgeInterruptFlags(InterruptFlag::EndOfRegularConversion | @@ -235,11 +245,66 @@ modm::platform::Adc{{ id }}::startConversion(void) } bool -modm::platform::Adc{{ id }}::isConversionFinished(void) +modm::platform::Adc{{ id }}::isConversionFinished() { return static_cast(getInterruptFlags() & InterruptFlag::EndOfRegularConversion); } +void +modm::platform::Adc{{ id }}::startInjectedConversionSequence() +{ + acknowledgeInterruptFlags(InterruptFlag::EndOfInjectedConversion | + InterruptFlag::EndOfInjectedSequenceOfConversions); + + ADC{{ id }}->CR |= ADC_CR_JADSTART; +} + +bool +modm::platform::Adc{{ id }}::setInjectedConversionChannel(uint8_t index, Channel channel, + SampleTime sampleTime) +{ + if (index >= 4) { + return false; + } + if (!configureChannel(channel, sampleTime)) { + return false; + } + + static_assert(ADC_JSQR_JSQ2_Pos == ADC_JSQR_JSQ1_Pos + 6); + static_assert(ADC_JSQR_JSQ3_Pos == ADC_JSQR_JSQ2_Pos + 6); + static_assert(ADC_JSQR_JSQ4_Pos == ADC_JSQR_JSQ3_Pos + 6); + + const uint32_t pos = ADC_JSQR_JSQ1_Pos + 6 * index; + const uint32_t mask = ADC_JSQR_JSQ1_Msk << (6 * index); + ADC{{ id }}->JSQR = (ADC{{ id }}->JSQR & ~mask) | (static_cast(channel) << pos); + return true; +} + +template +bool +modm::platform::Adc{{ id }}::setInjectedConversionChannel(uint8_t index, + SampleTime sampleTime) +{ + return setInjectedConversionChannel(index, getPinChannel(), sampleTime); +} + +bool +modm::platform::Adc{{ id }}::setInjectedConversionSequenceLength(uint8_t length) +{ + if (length < 1 or length > 4) { + return false; + } + ADC{{ id }}->JSQR = (ADC{{ id }}->JSQR & ~ADC_JSQR_JL) + | ((length - 1) << ADC_JSQR_JL_Pos); + return true; +} + +bool +modm::platform::Adc{{ id }}::isInjectedConversionFinished() +{ + return static_cast(getInterruptFlags() & InterruptFlag::EndOfInjectedSequenceOfConversions); +} + // ---------------------------------------------------------------------------- void modm::platform::Adc{{ id }}::enableInterruptVector(const uint32_t priority, From 4d692276573720d59642f262d8a8b0019b4d5829 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 12 Jul 2023 12:35:24 +0200 Subject: [PATCH 037/159] [example] Add injected conversion ADC example for Nucleo H723ZG --- .../adc_injected_conversion/main.cpp | 56 +++++++++++++++++++ .../adc_injected_conversion/project.xml | 10 ++++ 2 files changed, 66 insertions(+) create mode 100644 examples/nucleo_h723zg/adc_injected_conversion/main.cpp create mode 100644 examples/nucleo_h723zg/adc_injected_conversion/project.xml diff --git a/examples/nucleo_h723zg/adc_injected_conversion/main.cpp b/examples/nucleo_h723zg/adc_injected_conversion/main.cpp new file mode 100644 index 0000000000..56ac8b1974 --- /dev/null +++ b/examples/nucleo_h723zg/adc_injected_conversion/main.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 + +using namespace Board; + +int main() +{ + Board::initialize(); + + Adc1::connect(); + Adc1::initialize(Adc1::ClockMode::SynchronousPrescaler4, + Adc1::ClockSource::NoClock, + Adc1::Prescaler::Disabled, + Adc1::CalibrationMode::SingleEndedInputsMode); + + MODM_LOG_INFO << "ADC Injected Conversion Test\n"; + + Adc1::setInjectedConversionSequenceLength(4); + Adc1::setInjectedConversionChannel(0, Adc1::SampleTime::Cycles17); + Adc1::setInjectedConversionChannel(1, Adc1::SampleTime::Cycles17); + Adc1::setInjectedConversionChannel(2, Adc1::SampleTime::Cycles17); + Adc1::setInjectedConversionChannel(3, Adc1::SampleTime::Cycles17); + + while (true) { + // start regular conversion + Adc1::setPinChannel(Adc1::SampleTime::Cycles17); + Adc1::startConversion(); + + Adc1::startInjectedConversionSequence(); + while (!Adc1::isInjectedConversionFinished()); + + MODM_LOG_INFO << "ADC1 CH15 (injected): " << Adc1::getInjectedConversionValue(0) << '\n'; + MODM_LOG_INFO << "ADC1 CH15 (injected): " << Adc1::getInjectedConversionValue(2) << '\n'; + MODM_LOG_INFO << "ADC1 CH10 (injected): " << Adc1::getInjectedConversionValue(1) << '\n'; + MODM_LOG_INFO << "ADC1 CH10 (injected): " << Adc1::getInjectedConversionValue(3) << '\n'; + + // wait for regular conversion to finish + while (!Adc1::isConversionFinished()); + MODM_LOG_INFO << "ADC1 CH10 (regular): " << Adc1::getValue() << "\n\n"; + + Leds::toggle(); + modm::delay_ms(500); + } + + return 0; +} diff --git a/examples/nucleo_h723zg/adc_injected_conversion/project.xml b/examples/nucleo_h723zg/adc_injected_conversion/project.xml new file mode 100644 index 0000000000..c79cb1f183 --- /dev/null +++ b/examples/nucleo_h723zg/adc_injected_conversion/project.xml @@ -0,0 +1,10 @@ + + modm:nucleo-h723zg + + + + + modm:build:scons + modm:platform:adc:1 + + From 8b841351df1d06bc8530ceeb65f402449f19f947 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Thu, 3 Aug 2023 10:04:33 +0200 Subject: [PATCH 038/159] [driver] BdSpiFlash with SpiStack instruction set --- .../driver/storage/block_device_spiflash.hpp | 114 ++++++++++-------- .../storage/block_device_spiflash_impl.hpp | 95 ++++++++++++--- 2 files changed, 141 insertions(+), 68 deletions(-) diff --git a/src/modm/driver/storage/block_device_spiflash.hpp b/src/modm/driver/storage/block_device_spiflash.hpp index ca3182827c..019544f0b1 100644 --- a/src/modm/driver/storage/block_device_spiflash.hpp +++ b/src/modm/driver/storage/block_device_spiflash.hpp @@ -1,6 +1,7 @@ // coding: utf-8 /* * Copyright (c) 2018, Raphael Lehmann + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen * * This file is part of the modm project. * @@ -24,9 +25,7 @@ namespace modm /** - * \brief Block device with SPI Flash (Microchip SST26VF064B) - * - * 64MBit flash chip in SOIJ-8, WDFN-8 or SOIC-16 + * \brief Block device with SPI Flash * * \tparam Spi The SpiMaster interface * \tparam Cs The GpioOutput pin connected to the flash chip select @@ -37,9 +36,10 @@ namespace modm * * \ingroup modm_driver_block_device_spi_flash * \author Raphael Lehmann + * \author Rasmus Kleist Hørlyck Sørensen */ template -class BdSpiFlash : public modm::BlockDevice, public modm::SpiDevice< Spi >, protected NestedResumable<5> +class BdSpiFlash : public modm::BlockDevice, public modm::SpiDevice< Spi >, protected NestedResumable<6> { public: /// Initializes the storage hardware @@ -145,11 +145,21 @@ class BdSpiFlash : public modm::BlockDevice, public modm::SpiDevice< Spi >, prot modm::ResumableResult readStatus(); +public: + + /** Software Die Select + * + * @param die The pre-assigned “Die ID#” of the die to select + */ + modm::ResumableResult + selectDie(uint8_t die); + public: static constexpr bd_size_t BlockSizeRead = 1; static constexpr bd_size_t BlockSizeWrite = 256; static constexpr bd_size_t BlockSizeErase = 4 * 1'024; static constexpr bd_size_t DeviceSize = flashSize; + static constexpr bd_size_t ExtendedAddressThreshold = 16 * 1'024 * 1'024; private: uint8_t instructionBuffer[7]; @@ -160,55 +170,53 @@ class BdSpiFlash : public modm::BlockDevice, public modm::SpiDevice< Spi >, prot size_t index; - // See http://ww1.microchip.com/downloads/en/DeviceDoc/20005119G.pdf p.13 enum class Instruction : uint8_t { - //Configuration - Nop = 0x00, /// No Operation - RstEn = 0x66, /// Reset Enable - Rst = 0x99, /// Reset Memory - //EQio = 0x38, /// Enable Quad IO - //RstQio = 0xff, /// reste Quad IO - RdSr = 0x05, /// Read Status Register - WrSr = 0x01, /// Write Status Register - RdCr = 0x35, /// Read Configuration Register - // Read - Read = 0x03, /// Read - ReadHS = 0x0B, /// Read High Speed - SQOR = 0x6B, /// SPI Quad Output Read - SQIOR = 0xEB, /// SPI Quad I/O Read - SDOR = 0x3B, /// SPI Dual Output Read - SDIOR = 0xBB, /// SPI Dual I/O Read - SB = 0xC0, /// Set Burst Length - RBSQI = 0x0C, /// SQI Read Burst with Wrap - RBSPI = 0xEC, /// SPI Read Burst with Wrap - // Identification - JedecId = 0x9f, /// JEDEC-ID Read - QuadJId = 0xAF, /// Quad I/O J-ID Read - SFDP = 0x5A, /// Serial Flash Discoverable Parameters - // Write - WrEn = 0x06, /// Write Enable - WrDi = 0x04, /// Write Disable - SE = 0x20, /// Erase 4 KBytes of Memory Array - BE = 0xD8, /// Erase 64, 32 or 8 KBytes of Memory Array - CE = 0xC7, /// Erase Full Array - PP = 0x02, /// Page Program - SQPP = 0x32, /// SQI Quad Page Program - WRSU = 0xB0, /// Suspends Program/Erase - WRRE = 0x30, /// Resumes Program/Erase - // Protection - RBpR = 0x72, /// Read Block-Protection Register - WBpR = 0x42, /// Write Block-Protection Register - LBpR = 0x8D, /// Lock Down Block-Protection Register - nVWLDR = 0xE8, /// non-Volatile Write Lock-Down Register - ULBPR = 0x98, /// Global Block Protection Unlock - RSID = 0x88, /// Read Security ID - PSID = 0xA5, /// Program User Security ID area - LSID = 0x85, /// Lockout Security ID Programming + SDS = 0xC2, ///< Software Die Select + WE = 0x06, ///< Write Enable + VSRWE = 0x50, ///< Volatile Status Register Write Enable + WD = 0x04, ///< Write Disable + RDI = 0xAB, ///< Read Device ID + RMDI = 0x90, ///< Read Manufactuerer/Device ID + RJI = 0x9F, ///< Read JEDEC ID + RUI = 0x4B, ///< Read Unique ID + RD = 0x03, ///< Read Data + RD4BA = 0x13, ///< Read Data with 4-Byte Address + FR = 0x0B, ///< Fast Read + FR4BA = 0x0C, ///< Fast Read with 4-Byte Address + PP = 0x02, ///< Page Program + PP4BA = 0x12, ///< Page Program with 4-Byte Address + SE = 0x20, ///< 4KB Sector Erase + SE4B = 0x21, ///< 4KB Sector Erase with 4-Byte Address + BE32 = 0x52, ///< 32KB Block Erase + BE = 0xD8, ///< 64KB Block Erase + BE4B = 0xDC, ///< 64KB Block Erase with 4-Byte Address + CE = 0xC7, ///< Chip Erase + RSR1 = 0x05, ///< Read Status Register-1 + WSR1 = 0x01, ///< Write Status Register-1 + RSR2 = 0x35, ///< Read Status Register-2 + WSR2 = 0x31, ///< Write Status Register-2 + RSR3 = 0x15, ///< Read Status Register-3 + WSR3 = 0x11, ///< Write Status Register-3 + RSFDPR = 0x5A, ///< Read SFDP Register + ESR = 0x44, ///< Erase Security Register + PSR = 0x42, ///< Program Security Register + RSR = 0x48, ///< Read Security Register + GBL = 0x7E, ///< Global Block Lock + GBU = 0x98, ///< Global Block Unlock + RBL = 0x3D, ///< Read Block Lock + IBL = 0x36, ///< Individual Block Lock + IBU = 0x39, ///< Individual Block Unlock + EPS = 0x75, ///< Erase / Program Suspend + EPR = 0x7A, ///< Erase / Program Resume + En4BAM = 0xB7, ///< Enter 4-Byte Address Mode + Ex4BAM = 0xE9, ///< Exit 4-Byte Address Mode + RstEn = 0x66, ///< Enable Reset + Rst = 0x99, ///< Reset Device }; -private: +public: /** Check if device is busy * * @return True if device is busy. @@ -222,10 +230,16 @@ class BdSpiFlash : public modm::BlockDevice, public modm::SpiDevice< Spi >, prot modm::ResumableResult waitWhileBusy(); - /** Send an operation instruction to the spi flash chip +private: + /** Send a non-addressed operation instruction to the spi flash chip + */ + modm::ResumableResult + spiOperation(Instruction instruction, const uint8_t* tx = nullptr, uint8_t* rx = nullptr, std::size_t length = 0, uint8_t dummyCycles = 0); + + /** Send an addressed operation instruction to the spi flash chip */ modm::ResumableResult - spiOperation(Instruction instruction, size_t dataLength = 0, uint8_t* rxData = nullptr, const uint8_t* txData = nullptr, uint32_t address = UINT32_MAX, uint8_t nrDummyCycles = 0); + spiOperation(Instruction instruction, bd_address_t address, const uint8_t* tx = nullptr, uint8_t* rx = nullptr, std::size_t length = 0, uint8_t dummyCycles = 0); }; } diff --git a/src/modm/driver/storage/block_device_spiflash_impl.hpp b/src/modm/driver/storage/block_device_spiflash_impl.hpp index 3e4a3431f0..682de9ac1e 100644 --- a/src/modm/driver/storage/block_device_spiflash_impl.hpp +++ b/src/modm/driver/storage/block_device_spiflash_impl.hpp @@ -1,6 +1,7 @@ // coding: utf-8 /* * Copyright (c) 2018, Raphael Lehmann + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen * * This file is part of the modm project. * @@ -32,10 +33,16 @@ modm::BdSpiFlash::initialize() RF_CALL(spiOperation(Instruction::Rst)); RF_CALL(waitWhileBusy()); - RF_CALL(spiOperation(Instruction::WrEn)); - RF_CALL(spiOperation(Instruction::ULBPR)); + RF_CALL(spiOperation(Instruction::WE)); + RF_CALL(spiOperation(Instruction::GBU)); RF_CALL(waitWhileBusy()); + // Enter 4-Byte Address mode for serial flash memory that support 256M-bit or more + if (DeviceSize > ExtendedAddressThreshold) { + RF_CALL(spiOperation(Instruction::En4BAM)); + RF_CALL(waitWhileBusy()); + } + RF_END_RETURN(true); } @@ -56,7 +63,7 @@ modm::BdSpiFlash::readId() { RF_BEGIN(); - RF_CALL(spiOperation(Instruction::JedecId, 3, resultBuffer)); + RF_CALL(spiOperation(Instruction::RJI, nullptr, resultBuffer, 3)); RF_END_RETURN(JedecId(resultBuffer[0], resultBuffer[1], resultBuffer[2])); } @@ -72,7 +79,7 @@ modm::BdSpiFlash::read(uint8_t* buffer, bd_address_t address RF_RETURN(false); } - RF_CALL(spiOperation(Instruction::ReadHS, size, buffer, nullptr, address, 1)); + RF_CALL(spiOperation(Instruction::FR, address, nullptr, buffer, size, 1)); RF_END_RETURN(true); } @@ -90,8 +97,8 @@ modm::BdSpiFlash::program(const uint8_t* buffer, bd_address_ index = 0; while(index < size) { - RF_CALL(spiOperation(Instruction::WrEn)); - RF_CALL(spiOperation(Instruction::PP, BlockSizeWrite, nullptr, &buffer[index], address+index)); + RF_CALL(spiOperation(Instruction::WE)); + RF_CALL(spiOperation(Instruction::PP, address + index, &buffer[index], nullptr, BlockSizeWrite)); RF_CALL(waitWhileBusy()); index += BlockSizeWrite; } @@ -113,8 +120,8 @@ modm::BdSpiFlash::erase(bd_address_t address, bd_size_t size index = 0; while(index < size) { - RF_CALL(spiOperation(Instruction::WrEn)); - RF_CALL(spiOperation(Instruction::SE, 0, nullptr, nullptr, address+index)); + RF_CALL(spiOperation(Instruction::WE)); + RF_CALL(spiOperation(Instruction::SE, address + index)); RF_CALL(waitWhileBusy()); index += BlockSizeErase; } @@ -152,21 +159,41 @@ modm::ResumableResult::StatusRegis modm::BdSpiFlash::readStatus() { RF_BEGIN(); - RF_CALL(spiOperation(Instruction::RdSr, 1, resultBuffer)); + RF_CALL(spiOperation(Instruction::RSR1, nullptr, resultBuffer, 1)); RF_END_RETURN(static_cast(resultBuffer[0])); } +template +modm::ResumableResult +modm::BdSpiFlash::selectDie(uint8_t die) +{ + RF_BEGIN(); + + RF_CALL(spiOperation(Instruction::SDS, &die, nullptr, 1)); + RF_CALL(spiOperation(Instruction::WE)); + RF_CALL(spiOperation(Instruction::GBU)); + RF_CALL(waitWhileBusy()); + + // Enter 4-Byte Address mode for serial flash memory that support 256M-bit or more + if (DeviceSize > ExtendedAddressThreshold) { + RF_CALL(spiOperation(Instruction::En4BAM)); + RF_CALL(waitWhileBusy()); + } + + RF_END(); +} template modm::ResumableResult modm::BdSpiFlash::isBusy() { RF_BEGIN(); - if(RF_CALL(readStatus()) & StatusRegister::Busy) + + if(RF_CALL(readStatus()) & StatusRegister::Busy) { RF_RETURN(true); - else - RF_RETURN(false); - RF_END(); + } + + RF_END_RETURN(false); } @@ -181,16 +208,48 @@ modm::BdSpiFlash::waitWhileBusy() RF_END(); } +template +modm::ResumableResult +modm::BdSpiFlash::spiOperation(Instruction instruction, const uint8_t* tx, uint8_t* rx, std::size_t length, uint8_t nrDummyCycles) +{ + RF_BEGIN(); + + i = 0; + instructionBuffer[i++] = static_cast(instruction); + for(uint8_t j = 0; j < nrDummyCycles; j++) { + instructionBuffer[i++] = 0x00; + } + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + RF_CALL(Spi::transfer(instructionBuffer, nullptr, i)); + + if(length > 0) { + RF_CALL(Spi::transfer(const_cast(tx), rx, length)); + } + + if (this->releaseMaster()) { + Cs::set(); + } + + RF_END(); +} template modm::ResumableResult -modm::BdSpiFlash::spiOperation(Instruction instruction, size_t dataLength, uint8_t* rxData, const uint8_t* txData, uint32_t address, uint8_t nrDummyCycles) +modm::BdSpiFlash::spiOperation(Instruction instruction, uint32_t address, const uint8_t* tx, uint8_t* rx, std::size_t length, uint8_t nrDummyCycles) { RF_BEGIN(); i = 0; instructionBuffer[i++] = static_cast(instruction); - if(address != UINT32_MAX) { + if constexpr (DeviceSize > ExtendedAddressThreshold) { + instructionBuffer[i++] = (address >> 24) & 0xFF; + instructionBuffer[i++] = (address >> 16) & 0xFF; + instructionBuffer[i++] = (address >> 8) & 0xFF; + instructionBuffer[i++] = address & 0xFF; + } else { instructionBuffer[i++] = (address >> 16) & 0xFF; instructionBuffer[i++] = (address >> 8) & 0xFF; instructionBuffer[i++] = address & 0xFF; @@ -204,13 +263,13 @@ modm::BdSpiFlash::spiOperation(Instruction instruction, size RF_CALL(Spi::transfer(instructionBuffer, nullptr, i)); - if(dataLength > 0) { - RF_CALL(Spi::transfer(const_cast(txData), rxData, dataLength)); + if(length > 0) { + RF_CALL(Spi::transfer(const_cast(tx), rx, length)); } if (this->releaseMaster()) { Cs::set(); } - RF_END_RETURN(true); + RF_END(); } From 005e05abb903bdbdd318cd31553ca9d28630fffe Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Thu, 3 Aug 2023 10:05:48 +0200 Subject: [PATCH 039/159] [driver] Add BdSpiFlash chip erase implementation --- .../driver/storage/block_device_spiflash_impl.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modm/driver/storage/block_device_spiflash_impl.hpp b/src/modm/driver/storage/block_device_spiflash_impl.hpp index 682de9ac1e..9377fe17b0 100644 --- a/src/modm/driver/storage/block_device_spiflash_impl.hpp +++ b/src/modm/driver/storage/block_device_spiflash_impl.hpp @@ -118,12 +118,17 @@ modm::BdSpiFlash::erase(bd_address_t address, bd_size_t size RF_RETURN(false); } - index = 0; - while(index < size) { - RF_CALL(spiOperation(Instruction::WE)); - RF_CALL(spiOperation(Instruction::SE, address + index)); + if (address == 0 && size == flashSize) { + RF_CALL(spiOperation(Instruction::CE)); RF_CALL(waitWhileBusy()); - index += BlockSizeErase; + } else { + index = 0; + while(index < size) { + RF_CALL(spiOperation(Instruction::WE)); + RF_CALL(spiOperation(Instruction::SE, address + index)); + RF_CALL(waitWhileBusy()); + index += BlockSizeErase; + } } RF_END_RETURN(true); From 876688935eb8ed842a3f7b8007bd322103af60d8 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 7 Aug 2023 10:42:16 +0200 Subject: [PATCH 040/159] [driver] Flip BdSpiFlash waitWhileBusy --- .../driver/storage/block_device_spiflash_impl.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modm/driver/storage/block_device_spiflash_impl.hpp b/src/modm/driver/storage/block_device_spiflash_impl.hpp index 9377fe17b0..d55f205a7d 100644 --- a/src/modm/driver/storage/block_device_spiflash_impl.hpp +++ b/src/modm/driver/storage/block_device_spiflash_impl.hpp @@ -77,6 +77,8 @@ modm::BdSpiFlash::read(uint8_t* buffer, bd_address_t address if((size == 0) || (size % BlockSizeRead != 0) || (address + size > flashSize)) { RF_RETURN(false); + } else { + RF_CALL(waitWhileBusy()); } RF_CALL(spiOperation(Instruction::FR, address, nullptr, buffer, size, 1)); @@ -93,13 +95,14 @@ modm::BdSpiFlash::program(const uint8_t* buffer, bd_address_ if((size == 0) || (size % BlockSizeWrite != 0) || (address + size > flashSize)) { RF_RETURN(false); + } else { + RF_CALL(waitWhileBusy()); } index = 0; while(index < size) { RF_CALL(spiOperation(Instruction::WE)); RF_CALL(spiOperation(Instruction::PP, address + index, &buffer[index], nullptr, BlockSizeWrite)); - RF_CALL(waitWhileBusy()); index += BlockSizeWrite; } @@ -116,17 +119,17 @@ modm::BdSpiFlash::erase(bd_address_t address, bd_size_t size if((size == 0) || (size % BlockSizeErase != 0) || (address + size > flashSize)) { RF_RETURN(false); + } else { + RF_CALL(waitWhileBusy()); } if (address == 0 && size == flashSize) { RF_CALL(spiOperation(Instruction::CE)); - RF_CALL(waitWhileBusy()); } else { index = 0; while(index < size) { RF_CALL(spiOperation(Instruction::WE)); RF_CALL(spiOperation(Instruction::SE, address + index)); - RF_CALL(waitWhileBusy()); index += BlockSizeErase; } } @@ -175,6 +178,8 @@ modm::BdSpiFlash::selectDie(uint8_t die) RF_BEGIN(); RF_CALL(spiOperation(Instruction::SDS, &die, nullptr, 1)); + RF_CALL(waitWhileBusy()); + RF_CALL(spiOperation(Instruction::WE)); RF_CALL(spiOperation(Instruction::GBU)); RF_CALL(waitWhileBusy()); From fb41780cee2f3648d9559e02fb380043e1668d88 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Fri, 11 Aug 2023 17:12:03 +0200 Subject: [PATCH 041/159] [example] Wait test manually after erasing flash --- examples/nucleo_f429zi/spi_flash/main.cpp | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/nucleo_f429zi/spi_flash/main.cpp b/examples/nucleo_f429zi/spi_flash/main.cpp index 9ea09b60a0..d27b231fe2 100644 --- a/examples/nucleo_f429zi/spi_flash/main.cpp +++ b/examples/nucleo_f429zi/spi_flash/main.cpp @@ -107,24 +107,24 @@ main() std::memset(bufferB, 0x55, BlockSize); bool initializeSuccess = false; - - MODM_LOG_INFO << "Erasing complete flash chip... (This may take a while)" << modm::endl; - - if(!RF_CALL_BLOCKING(storageDevice.initialize())) { + if (RF_CALL_BLOCKING(storageDevice.initialize())) { + MODM_LOG_INFO << "Erasing complete flash chip... (This may take a while)" << modm::endl; + if (RF_CALL_BLOCKING(storageDevice.erase(0, MemorySize))) { + RF_CALL_BLOCKING(storageDevice.waitWhileBusy()); + initializeSuccess = true; + } else { + MODM_LOG_INFO << "Error: Unable to erase device."; + } + } else { MODM_LOG_INFO << "Error: Unable to initialize device."; } - else if(!RF_CALL_BLOCKING(storageDevice.erase(0, MemorySize))) { - MODM_LOG_INFO << "Error: Unable to erase device."; - } - else { + + if (initializeSuccess) { auto id = RF_CALL_BLOCKING(storageDevice.readId()); MODM_LOG_INFO << "deviceId=" << id.deviceId << " manufacturerId=" << id.manufacturerId; - MODM_LOG_INFO << " deviceType=" << id.deviceType << modm::endl; - + MODM_LOG_INFO << "deviceType=" << id.deviceType << modm::endl; MODM_LOG_INFO << "status=" << static_cast(RF_CALL_BLOCKING(storageDevice.readStatus())) << modm::endl; - MODM_LOG_INFO << "Press USER button to start the memory test." << modm::endl; - initializeSuccess = true; } while (true) From ebe411478881008f23a5f93a3d3d25fcdfbae5bd Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Thu, 3 Aug 2023 11:41:52 +0200 Subject: [PATCH 042/159] [docs] Let synchronize_docs ignore SPI-STACK-FLASH --- tools/scripts/synchronize_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/scripts/synchronize_docs.py b/tools/scripts/synchronize_docs.py index 11ef2b8b0d..969051ecd1 100755 --- a/tools/scripts/synchronize_docs.py +++ b/tools/scripts/synchronize_docs.py @@ -77,7 +77,7 @@ def name(raw_name): .replace("SPI-FLASH", "SPI Flash")\ .replace("-SPI", "") if result in ["DEVICE", "LIS3-TRANSPORT", "MEMORY-BUS", "TERMINAL", "ALLOCATOR", - "MIRROR", "ADC-SAMPLER", "FAT", "HEAP", "--PYCACHE--", "FILE"]: + "MIRROR", "ADC-SAMPLER", "FAT", "HEAP", "--PYCACHE--", "FILE", "SPI-STACK-FLASH"]: return None return result From a1ad2a02c413ad9ea533d6821407977cd05084e0 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Thu, 3 Aug 2023 10:07:26 +0200 Subject: [PATCH 043/159] [driver] Add BdSpiStackFlash contiguous memory --- src/modm/driver/storage/block_device.lb | 16 ++ .../storage/block_device_spistack_flash.hpp | 125 ++++++++++++ .../block_device_spistack_flash_impl.hpp | 180 ++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 src/modm/driver/storage/block_device_spistack_flash.hpp create mode 100644 src/modm/driver/storage/block_device_spistack_flash_impl.hpp diff --git a/src/modm/driver/storage/block_device.lb b/src/modm/driver/storage/block_device.lb index bcaa65c16f..94bd0a2206 100644 --- a/src/modm/driver/storage/block_device.lb +++ b/src/modm/driver/storage/block_device.lb @@ -71,6 +71,21 @@ Microchip SST26VF064B 64MBit flash chip in SOIJ-8, WDFN-8 or SOIC-16. env.copy("block_device_spiflash_impl.hpp") # ----------------------------------------------------------------------------- +class BlockDeviceSpiStackFlash(Module): + def init(self, module): + module.name = "spi.stack.flash" + module.description = "SpiStack homogeneous flash memory" + + def prepare(self, module, options): + module.depends(":architecture:block.device") + return True + + def build(self, env): + env.outbasepath = "modm/src/modm/driver/storage" + env.copy("block_device_spistack_flash.hpp") + env.copy("block_device_spistack_flash_impl.hpp") +# ----------------------------------------------------------------------------- + def init(module): module.name = ":driver:block.device" module.description = "Block Devices" @@ -80,6 +95,7 @@ def prepare(module, options): module.add_submodule(BlockDeviceHeap()) module.add_submodule(BlockDeviceMirror()) module.add_submodule(BlockDeviceSpiFlash()) + module.add_submodule(BlockDeviceSpiStackFlash()) return True def build(env): diff --git a/src/modm/driver/storage/block_device_spistack_flash.hpp b/src/modm/driver/storage/block_device_spistack_flash.hpp new file mode 100644 index 0000000000..4355110f87 --- /dev/null +++ b/src/modm/driver/storage/block_device_spistack_flash.hpp @@ -0,0 +1,125 @@ +// coding: utf-8 +/* + * Copyright (c) 2018, Raphael Lehmann + * + * 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_BLOCK_DEVICE_SPISTACK_FLASH_HPP +#define MODM_BLOCK_DEVICE_SPISTACK_FLASH_HPP + +#include +#include + +#include +#include + +namespace modm +{ + +/** + * \brief SpiStack homogenoues memory + * + * The `read()`, `erase()`,`program()` and `write()` methodes wait for + * the chip to finish writing to the flash. + * + * \tparam SpiBlockDevice Base SPI block device of the homogenous stack + * + * \ingroup modm_driver_block_device_spi_stack_flash + * \author Rasmus Kleist Hørlyck Sørensen + */ +template +class BdSpiStackFlash : public modm::BlockDevice, protected NestedResumable<3> +{ +public: + /// Initializes the storage hardware + modm::ResumableResult + initialize(); + + /// Deinitializes the storage hardware + modm::ResumableResult + deinitialize(); + + /** Read data from one or more blocks + * + * @param buffer Buffer to read data into + * @param address Address to begin reading from + * @param size Size to read in bytes (multiple of read block size) + * @return True on success + */ + modm::ResumableResult + read(uint8_t* buffer, bd_address_t address, bd_size_t size); + + /** Program blocks with data + * + * Any block has to be erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param address Address of first block to begin writing to + * @param size Size to write in bytes (multiple of read block size) + * @return True on success + */ + modm::ResumableResult + program(const uint8_t* buffer, bd_address_t address, bd_size_t size); + + /** Erase blocks + * + * The state of an erased block is undefined until it has been programmed + * + * @param address Address of block to begin erasing + * @param size Size to erase in bytes (multiple of read block size) + * @return True on success + */ + modm::ResumableResult + erase(bd_address_t address, bd_size_t size); + + /** Writes data to one or more blocks after erasing them + * + * The blocks are erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param address Address of first block to begin writing to + * @param size Size to write in bytes (multiple of read block size) + * @return True on success + */ + modm::ResumableResult + write(const uint8_t* buffer, bd_address_t address, bd_size_t size); + +public: + /** Check if device is busy + * + * @return True if any die in the stack is busy. + */ + modm::ResumableResult + isBusy(); + + /** This function can be used in another resumable function + * to wait until the flash operation is finished. + */ + modm::ResumableResult + waitWhileBusy(); + +public: + static constexpr bd_size_t BlockSizeRead = SpiBlockDevice::BlockSizeRead; + static constexpr bd_size_t BlockSizeWrite = SpiBlockDevice::BlockSizeWrite; + static constexpr bd_size_t BlockSizeErase = SpiBlockDevice::BlockSizeErase; + static constexpr bd_size_t DieSize = SpiBlockDevice::DeviceSize; + static constexpr bd_size_t DeviceSize = DieCount * DieSize; + +private: + std::ldiv_t dv; + uint32_t index; + uint8_t currentDie; + SpiBlockDevice spiBlockDevice; +}; + +} // namespace modm + +#include "block_device_spistack_flash_impl.hpp" + +#endif // MODM_BLOCK_DEVICE_SPISTACK_FLASH_HPP diff --git a/src/modm/driver/storage/block_device_spistack_flash_impl.hpp b/src/modm/driver/storage/block_device_spistack_flash_impl.hpp new file mode 100644 index 0000000000..8d33e73ce2 --- /dev/null +++ b/src/modm/driver/storage/block_device_spistack_flash_impl.hpp @@ -0,0 +1,180 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_BLOCK_DEVICE_SPISTACK_FLASH_HPP +#error "Don't include this file directly, use 'block_device_spistack_flash.hpp' instead!" +#endif + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::initialize() +{ + RF_BEGIN(); + + if (RF_CALL(spiBlockDevice.initialize())) { + RF_CALL(spiBlockDevice.selectDie(currentDie = 0x00)); + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::deinitialize() +{ + RF_BEGIN(); + RF_END_RETURN_CALL(spiBlockDevice.deinitialize()); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::read(uint8_t* buffer, bd_address_t address, bd_size_t size) +{ + RF_BEGIN(); + + if((size == 0) || (size % BlockSizeRead != 0) || (address + size > DeviceSize)) { + RF_RETURN(false); + } + + index = 0; + while (index < size) { + dv = std::ldiv(index + address, DieSize); // dv.quot = die #ID, dv.rem = die address + if (currentDie != dv.quot) { + RF_CALL(spiBlockDevice.selectDie(currentDie = dv.quot)); + } + if (RF_CALL(spiBlockDevice.read(&buffer[index], dv.rem, std::min(size - index, DieSize - dv.rem)))) { + index += DieSize - dv.rem; // size - index <= DieSize - dv.rem only on last iteration! + } else { + RF_RETURN(false); + } + } + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::program(const uint8_t* buffer, bd_address_t address, bd_size_t size) +{ + RF_BEGIN(); + + if((size == 0) || (size % BlockSizeWrite != 0) || (address + size > DeviceSize)) { + RF_RETURN(false); + } + + index = 0; + while (index < size) { + dv = std::ldiv(index + address, DieSize); // dv.quot = die #ID, dv.rem = die address + if (currentDie != dv.quot) { + RF_CALL(spiBlockDevice.selectDie(currentDie = dv.quot)); + } + if (RF_CALL(spiBlockDevice.program(&buffer[index], dv.rem, std::min(size - index, DieSize - dv.rem)))) { + index += DieSize - dv.rem; // size - index <= DieSize - dv.rem only on last iteration! + } else { + RF_RETURN(false); + } + } + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::erase(bd_address_t address, bd_size_t size) +{ + RF_BEGIN(); + + if((size == 0) || (size % BlockSizeErase != 0) || (address + size > DeviceSize)) { + RF_RETURN(false); + } + + index = 0; + while (index < size) { + dv = std::ldiv(index + address, DieSize); // dv.quot = die #ID, dv.rem = die address + if (currentDie != dv.quot) { + RF_CALL(spiBlockDevice.selectDie(currentDie = dv.quot)); + } + if (RF_CALL(spiBlockDevice.erase(dv.rem, std::min(size - index, DieSize - dv.rem)))) { + index += DieSize - dv.rem; // size - index <= DieSize - dv.rem only on last iteration! + } else { + RF_RETURN(false); + } + } + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::write(const uint8_t* buffer, bd_address_t address, bd_size_t size) +{ + RF_BEGIN(); + + if((size == 0) || (size % BlockSizeErase != 0) || (size % BlockSizeWrite != 0) || (address + size > DeviceSize)) { + RF_RETURN(false); + } + + if(!RF_CALL(this->erase(address, size))) { + RF_RETURN(false); + } + + if(!RF_CALL(this->program(buffer, address, size))) { + RF_RETURN(false); + } + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::isBusy() +{ + RF_BEGIN(); + + currentDie = DieCount; + while (currentDie > 0) { + RF_CALL(spiBlockDevice.selectDie(--currentDie)); + if (RF_CALL(spiBlockDevice.isBusy())) { + RF_RETURN(true); + } + } + + RF_END_RETURN(false); +} + +// ---------------------------------------------------------------------------- + +template +modm::ResumableResult +modm::BdSpiStackFlash::waitWhileBusy() +{ + RF_BEGIN(); + while(RF_CALL(isBusy())) { + RF_YIELD(); + } + RF_END(); +} From ba23833ad0960a57bf63194ce06175c5b169ab56 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Thu, 3 Aug 2023 10:07:39 +0200 Subject: [PATCH 044/159] [example] Add BdSpiStackFlash test --- .../nucleo_f429zi/spistack_flash/main.cpp | 157 ++++++++++++++++++ .../nucleo_f429zi/spistack_flash/project.xml | 12 ++ 2 files changed, 169 insertions(+) create mode 100644 examples/nucleo_f429zi/spistack_flash/main.cpp create mode 100644 examples/nucleo_f429zi/spistack_flash/project.xml diff --git a/examples/nucleo_f429zi/spistack_flash/main.cpp b/examples/nucleo_f429zi/spistack_flash/main.cpp new file mode 100644 index 0000000000..82f0421cfa --- /dev/null +++ b/examples/nucleo_f429zi/spistack_flash/main.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 + +using namespace Board; + +void printMemoryContent(const uint8_t* address, std::size_t size) { + for (std::size_t i = 0; i < size; i++) { + MODM_LOG_INFO.printf("%x", address[i]); + } +} + +using SpiMaster = SpiMaster1; + +// Spi flash chip wiring: +using Cs = GpioA4; +using Mosi = GpioB5; +using Miso = GpioB4; +using Sck = GpioB3; +// Connect WP and HOLD pins to +3V3 +// and of course Vdd to +3V3 and Vss to GND + + +constexpr uint32_t BlockSize = 256; +constexpr uint32_t DieSize = 32*1024*1024; +constexpr uint32_t DieCount = 2; +constexpr uint32_t MemorySize = DieCount * DieSize; +constexpr uint32_t TestMemorySize = 4*1024; +constexpr uint32_t TestMemoryAddress[] = {0, DieSize - TestMemorySize, DieSize, MemorySize - TestMemorySize}; + +uint8_t bufferA[BlockSize]; +uint8_t bufferB[BlockSize]; +uint8_t bufferC[BlockSize]; + +using BdSpiFlash = modm::BdSpiFlash; +using BdSpiStackFlash = modm::BdSpiStackFlash; + +BdSpiFlash storageDevice; +BdSpiStackFlash storageDeviceStack; + +void doMemoryTest() +{ + LedBlue::set(); + MODM_LOG_INFO << "Starting memory test!" << modm::endl; + + for(uint16_t iteration = 0; iteration < 4; iteration++) { + + for(const uint32_t address : TestMemoryAddress) { + + auto dv = std::ldiv(address, DieSize); + uint8_t* pattern = (iteration % 2 == dv.quot) ? bufferA : bufferB; + if(!RF_CALL_BLOCKING(storageDeviceStack.erase(address, TestMemorySize))) { + MODM_LOG_INFO << "Error: Unable to erase device."; + return; + } + + for(uint32_t i = 0; i < TestMemorySize; i += BlockSize) { + if(!RF_CALL_BLOCKING(storageDeviceStack.program(pattern, address + i, BlockSize))) { + MODM_LOG_INFO << "Error: Unable to write data."; + return; + } + MODM_LOG_INFO << "."; + } + } + + for(const uint32_t address : TestMemoryAddress) { + + auto dv = std::ldiv(address, DieSize); + uint8_t* pattern = (iteration % 2 == dv.quot) ? bufferA : bufferB; + for(uint32_t i = 0; i < TestMemorySize; i += BlockSize) { + if(!RF_CALL_BLOCKING(storageDeviceStack.read(bufferC, address + i, BlockSize))) { + MODM_LOG_INFO << "Error: Unable to read data."; + return; + } + else if(std::memcmp(pattern, bufferC, BlockSize)) { + MODM_LOG_INFO << "i=" << i << modm::endl; + MODM_LOG_INFO << "Error: Read '"; + printMemoryContent(bufferC, BlockSize); + MODM_LOG_INFO << "', expected: '"; + printMemoryContent(pattern, BlockSize); + MODM_LOG_INFO << "'." << modm::endl; + return; + } + } + } + + MODM_LOG_INFO << "." << modm::endl; + } + + MODM_LOG_INFO << modm::endl << "Finished!" << modm::endl; + LedBlue::reset(); +} + +int +main() +{ + /** + * This example/test writes alternating patterns to a 256 MBit + * stacked die flash chip (W25M512VJ) attached to SPI0 using the + * `modm::BdSpiStackFlash` block device interface. + * The memory content is afterwards read and compared + * to the pattern. + * Write and read operations are done on 64 byte blocks. + * + * See above for how to wire the flash chip. + */ + + // initialize board and SPI + Board::initialize(); + SpiMaster::connect(); + SpiMaster::initialize(); + + std::memset(bufferA, 0xAA, BlockSize); + std::memset(bufferB, 0x55, BlockSize); + + bool initializeSuccess = false; + if (RF_CALL_BLOCKING(storageDeviceStack.initialize())) { + MODM_LOG_INFO << "Erasing complete flash chip... (This may take a while)" << modm::endl; + if (RF_CALL_BLOCKING(storageDeviceStack.erase(0, MemorySize))) { + RF_CALL_BLOCKING(storageDeviceStack.waitWhileBusy()); + initializeSuccess = true; + } else { + MODM_LOG_INFO << "Error: Unable to erase device."; + } + } else { + MODM_LOG_INFO << "Error: Unable to initialize device."; + } + + if (initializeSuccess) { + auto id = RF_CALL_BLOCKING(storageDevice.readId()); + MODM_LOG_INFO << "deviceId=" << id.deviceId << " manufacturerId=" << id.manufacturerId; + MODM_LOG_INFO << "deviceType=" << id.deviceType << modm::endl; + MODM_LOG_INFO << "status=" << static_cast(RF_CALL_BLOCKING(storageDevice.readStatus())) << modm::endl; + MODM_LOG_INFO << "Press USER button to start the memory test." << modm::endl; + } + + while (true) + { + if(initializeSuccess && Button::read()) + { + doMemoryTest(); + } + } + + return 0; +} diff --git a/examples/nucleo_f429zi/spistack_flash/project.xml b/examples/nucleo_f429zi/spistack_flash/project.xml new file mode 100644 index 0000000000..29980df508 --- /dev/null +++ b/examples/nucleo_f429zi/spistack_flash/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-f429zi + + + + + modm:driver:block.device:spi.flash + modm:driver:block.device:spi.stack.flash + modm:platform:spi:1 + modm:build:scons + + \ No newline at end of file From d214fb71141f8cc263303105a2d17ddcfca505ff Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 7 Aug 2023 20:12:45 +0200 Subject: [PATCH 045/159] [architecture] Adding UartDevice for resumable r/w --- .../architecture/interface/uart_device.hpp | 156 ++++++++++++++++++ src/modm/architecture/module.lb | 15 ++ 2 files changed, 171 insertions(+) create mode 100644 src/modm/architecture/interface/uart_device.hpp diff --git a/src/modm/architecture/interface/uart_device.hpp b/src/modm/architecture/interface/uart_device.hpp new file mode 100644 index 0000000000..1be7ff47b6 --- /dev/null +++ b/src/modm/architecture/interface/uart_device.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_INTERFACE_UART_DEVICE_HPP +#define MODM_INTERFACE_UART_DEVICE_HPP + +#include "uart.hpp" +#include + +namespace modm +{ + +/** + * Base class of an UART Device. + * + * This class provides generic transaction-like semantics + * + * @author Rasmus Kleist Hørlyck Sørensen + * @ingroup modm_architecture_uart_device + */ +template < class Uart, uint8_t NestingLevels = 10 > +class UartDevice : protected modm::NestedResumable< NestingLevels + 1 > +{ +public: + UartDevice() : + txTimeout(std::chrono::microseconds(1000)), + rxTimeout(std::chrono::microseconds(10000)) + { + } + + bool + hasReceived() + { + return Uart::receiveBufferSize() > 0; + } + + void + setTxTimeout(ShortPreciseDuration timeout) + { + txTimeout = timeout; + } + + void + setRxTimeout(ShortPreciseDuration timeout) + { + rxTimeout = timeout; + } + +protected: + modm::ResumableResult + write(uint8_t data) + { + RF_BEGIN(0); + + timeout.restart(txTimeout); + RF_WAIT_UNTIL(Uart::write(data) or timeout.isExpired() or Uart::hasError()); + if (timeout.isExpired() or Uart::hasError()) + { + Uart::discardTransmitBuffer(); + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_END_RETURN(true); + } + + modm::ResumableResult + write(const uint8_t *data, std::size_t length) + { + RF_BEGIN(0); + + writeIndex = 0; + timeout.restart(txTimeout); + while (writeIndex < length) + { + if (size_t writeSize = Uart::write(&data[writeIndex], length - writeIndex)) + { + writeIndex += writeSize; + timeout.restart(txTimeout); + } + + if (timeout.isExpired() or Uart::hasError()) + { + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_YIELD(); + } + + RF_END_RETURN(true); + } + + modm::ResumableResult + read(uint8_t &data) + { + RF_BEGIN(1); + + timeout.restart(rxTimeout); + RF_WAIT_UNTIL(Uart::read(data) or timeout.isExpired() or Uart::hasError()); + if (timeout.isExpired() or Uart::hasError()) + { + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_END_RETURN(true); + } + + modm::ResumableResult + read(uint8_t *buffer, std::size_t length) + { + RF_BEGIN(1); + + readIndex = 0; + timeout.restart(rxTimeout); + while (readIndex < length) + { + if (size_t readSize = Uart::read(&buffer[readIndex], length - readIndex)) + { + readIndex += readSize; + timeout.restart(rxTimeout); + } + + if (timeout.isExpired() or Uart::hasError()) + { + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_YIELD(); + } + + RF_END_RETURN(true); + } + +private: + std::size_t readIndex; + std::size_t writeIndex; + + ShortPreciseDuration txTimeout; + ShortPreciseDuration rxTimeout; + ShortPreciseTimeout timeout; +}; + +} // namespace modm + +#endif // MODM_INTERFACE_UART_DEVICE_HPP diff --git a/src/modm/architecture/module.lb b/src/modm/architecture/module.lb index aba5fe10b9..7ce177c87f 100644 --- a/src/modm/architecture/module.lb +++ b/src/modm/architecture/module.lb @@ -339,6 +339,20 @@ class Uart(Module): env.copy("interface/uart.hpp") # ----------------------------------------------------------------------------- +class UartDevice(Module): + def init(self, module): + module.name = "uart.device" + module.description = "UART Devices" + + def prepare(self, module, options): + module.depends(":architecture:uart") + return True + + def build(self, env): + env.outbasepath = "modm/src/modm/architecture" + env.copy("interface/uart_device.hpp") +# ----------------------------------------------------------------------------- + class Unaligned(Module): def init(self, module): module.name = "unaligned" @@ -386,6 +400,7 @@ def prepare(module, options): module.add_submodule(Spi()) module.add_submodule(SpiDevice()) module.add_submodule(Uart()) + module.add_submodule(UartDevice()) module.add_submodule(Unaligned()) return True From 194f3bc266c54efab23b6dcefd4959c3dd8bf241 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 7 Aug 2023 20:14:09 +0200 Subject: [PATCH 046/159] [driver] Adding Semtech SX1280/SX1281 driver --- README.md | 5 +- src/modm/driver/radio/sx128x.hpp | 247 ++++ src/modm/driver/radio/sx128x.lb | 40 + src/modm/driver/radio/sx128x_definitions.hpp | 1002 +++++++++++++++++ src/modm/driver/radio/sx128x_impl.hpp | 608 ++++++++++ src/modm/driver/radio/sx128x_transport.hpp | 138 +++ .../driver/radio/sx128x_transport_impl.hpp | 162 +++ 7 files changed, 2200 insertions(+), 2 deletions(-) create mode 100644 src/modm/driver/radio/sx128x.hpp create mode 100644 src/modm/driver/radio/sx128x.lb create mode 100644 src/modm/driver/radio/sx128x_definitions.hpp create mode 100644 src/modm/driver/radio/sx128x_impl.hpp create mode 100644 src/modm/driver/radio/sx128x_transport.hpp create mode 100644 src/modm/driver/radio/sx128x_transport_impl.hpp diff --git a/README.md b/README.md index ec66e03826..fc4f82adb6 100644 --- a/README.md +++ b/README.md @@ -802,17 +802,18 @@ your specific needs. STTS22H STUSB4500 SX1276 +SX128X TCS3414 TCS3472 -TLC594x +TLC594x TMP102 TMP12x TMP175 TOUCH2046 VL53L0 -VL6180 +VL6180 WS2812 diff --git a/src/modm/driver/radio/sx128x.hpp b/src/modm/driver/radio/sx128x.hpp new file mode 100644 index 0000000000..4e81862a13 --- /dev/null +++ b/src/modm/driver/radio/sx128x.hpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2023, Lasse Alexander Jensen + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_SX128X_HPP +#define MODM_SX128X_HPP + +#include "sx128x_definitions.hpp" +#include "sx128x_transport.hpp" + +#include + +namespace modm +{ + +/** + * @tparam Transport Transpot Layer for SX128x + * + * @ingroup modm_driver_sx128x + * @author Lasse Alexander Jensen + * @author Rasmus Kleist Hørlyck Sørensen + */ +template< class Transport, class Reset, class Busy > +class Sx128x : public sx128x, public Transport +{ + using Command = Transport::Command; + +public: + Sx128x() = default; + + /// Determine if radio is busy + bool + isBusy(); + + /// Reset the transceiver + /// @attention It is necessary to reset each radio on a shared bus prior to starting the first SPI communicatio + modm::ResumableResult + reset(); + +public: + /// Get the transceiver status directly. + /// @attention this command can be issued at any time as long as it is not the very first command send over the interface + modm::ResumableResult + getStatus(Status *status); + + /// Writes a single byte in a data memory space at the specified address + modm::ResumableResult + writeRegister(Register reg, uint8_t data); + + /// Writes a block of bytes in a data memory space starting at a specific address + modm::ResumableResult + writeRegister(Register reg, std::span data); + + /// Read a single byte of data at the given address + modm::ResumableResult + readRegister(Register reg, uint8_t *data); + + /// Read a block of data starting at a given address + modm::ResumableResult + readRegister(Register reg, std::span data); + + /// This function is used to write the data payload to be transmitted + /// @attention When the address exceeds 255 it wraps back to 0 due to the circular nature of data buffer + modm::ResumableResult + writeBuffer(uint8_t offset, std::span data); + + /// This function is used to read the received data payload + modm::ResumableResult + readBuffer(uint8_t offset, std::span data); + + /// Set the transceiver to Sleep mode with the lowest current consumption possible + /// @warning Depending on the specified level of memory retention not all instruction will be retained + modm::ResumableResult + setSleep(SleepConfig_t sleepConfig); + + /// Set the device in either STDBY_RC or STDBY_XOSC mode which are intermediate levels of power consumption + /// By default in this state, the system is clocked by the 13 MHz RC oscillator to reduce power consumption. + /// However if the application is time critical, the XOSC block can be turned or left ON. + /// @attention In this Standby mode, the transceiver may be configured for future RF operations + modm::ResumableResult + setStandby(StandbyConfig standbyConfig = StandbyConfig::StdbyRc); + + /// Set the device in Frequency Synthesizer mode where the PLL is locked to the carrier frequency + /// @attention In normal operation of the transceiver, the user does not normally need to use FS mode + modm::ResumableResult + setFs(); + + /// Set the device in Transmit mode + /// @warning The IRQ status must be cleared before using this command + modm::ResumableResult + setTx(PeriodBase periodBase, uint16_t periodBaseCount); + + /// Set the device in Receiver mode + /// @warning The IRQ status must be cleared before using this command + /// @attention Setting periodBaseCount = 0 puts the transceiver in RX Single Mode + /// @attention Setting periodBaseCount = 0xFFFF puts the transceiver in Rx Continuous mode + modm::ResumableResult + setRx(PeriodBase periodBase, uint16_t periodBaseCount); + + /// Set the transceiver in sniff mode, so that it regularly looks for new packets + /// @warning RxDone interrupt must be configured prior to enter Duty cycled operations + /// @warning SetLongPreamble must be issued prior to setRxDutyCycle + modm::ResumableResult + setRxDutyCycle(PeriodBase periodBase, uint16_t rxPeriodBaseCount, uint16_t sleepPeriodBaseCount); + + /// Set the transceiver to use CAD (Channel Activity Detection) mode + /// @warning The Channel Activity Detection is a LoRa specific mode of operation + modm::ResumableResult + setCad(); + + /// Set the transceiver to generate a Continuous Wave (RF tone) at a selected frequency and output power + /// @attention The device remains in Tx Continuous Wave until the host sends a mode confiquration command. + modm::ResumableResult + setTxContinuousWave(); + + /// Set the transceiver to generate an infinite sequence of alternating 'O's and '1's in GFSK modulation and symbol 0 in LoRa + /// @attention The device remains in transmit until the host sends a mode confiquration command + modm::ResumableResult + setTxContinuousPreamble(); + + /// Set the transceiver radio frame out of a choice of 5 different packet types + /// @attention the packet type must be set first in the radio configuration sequence + modm::ResumableResult + setPacketType(PacketType packetType); + + /// Get the current operating packet type of the radio + modm::ResumableResult + getPacketType(PacketType *packetType); + + /// Set the frequency of the RF frequency mode. + /// @warning This must be called after SetPacket type. + /// @attention The LSB of rfFrequency is equal to the PLL step i.e. 52e6/2^18 Hz, where 52e6 is the crystal frequency in Hz + modm::ResumableResult + setRfFrequency(uint32_t rfFrequency); + + /// Set the Tx output power and the Tx ramp time + /// @attention The output power (Pout) is defined by the parameter power. Pout = -18 + power + /// @attention PoutMin = -18 dBm (power = 0) and PoutMax = 13 dBm (power = 31) + modm::ResumableResult + setTxParams(uint8_t power, RampTime rampTime); + + /// Set the number of symbols on which which Channel Activity Detected (CAD) operates + modm::ResumableResult + setCadParams(CadSymbolNumber cadSymbolNumber); + + /// Set the base address for the packet handling operation in Tx and Rx mode for all packet types + modm::ResumableResult + setBufferBaseAddress(uint8_t txBaseAddress, uint8_t rxBaseAddress); + + /// Set the modulation parameters of the radio + /// @attention The modulation parameters will be interpreted depending on the frame type + modm::ResumableResult + setModulationParams(ModulationParams modulationParams); + + /// Set the parameters of the packet handling block + /// @warning Interpretation by the transceiver of the packet parameters depends upon the chosen packet type + modm::ResumableResult + setPacketParams(PacketParams packetParams); + + /// Get length of the last received packet and the address of the first byte received + modm::ResumableResult + getRxBufferStatus(RxBufferStatus *rxBufferStatus); + + /// Retrieve information about the last received packet + /// @attention The returned parameters are frame-dependent + /// @attention Actual signal power is -(rssiSync) / 2 (dBm) + /// @attention Actual SNR is (snr) / 4 (dBm) + modm::ResumableResult + getPacketStatus(PacketStatus *packetStatus); + + /// Get the instantaneous RSSI value during reception of the packet + /// @attention Actual signal power is -(rssiSync) / 2 (dBm) + modm::ResumableResult + getRssiInst(uint8_t *rssiInst); + + /// Enable IRQs and to route IRQs to DIO pins. + modm::ResumableResult + setDioIrqParams(Irq_t irqMask, Irq_t dio1Mask = Irq_t(), Irq_t dio2Mask = Irq_t(), Irq_t dio3Mask = Irq_t()); + + /// Get the value of the IRQ register + modm::ResumableResult + getIrqStatus(Irq_t *irqStatus); + + /// Clear IRQ flags in the IRQ register + modm::ResumableResult + clearIrqStatus(Irq_t irqMask); + + /// Specify if DC-DC or LDO is used for power regulation + modm::ResumableResult + setRegulatorMode(RegulatorMode regModeParam); + + /// Save the present context of the radio register values to the Data RAM + modm::ResumableResult + setSaveContext(); + + /// Set the chip so that the state following a Rx or Tx operation is FS and not STDBY + /// This feature is to be used to reduce the switching time between consecutive Rx and/or Tx operations + modm::ResumableResult + setAutoFs(bool enable = true); + + /// Set the device to be able to send back a response 150 us after a packet reception + /// @attention The delay between the packet reception end and the next packet transmission start is defined by TxDelay = time + 33 us + modm::ResumableResult + setAutoTx(uint16_t time); + + /// Set the transceiver into Long Preamble mode + /// @warning Long Preamble mode can only be used with either LoRa mode and GFSK mode + modm::ResumableResult + setLongPreamble(bool enable = true); + + /// Set UART speed + /// @warning UART only + /// @attention UART communication must be initiated with 115.2 kbps + /// @attention The UartDividerRatio is given by UartDividerRatio = (Baud Rate * 2^20) / fClk + modm::ResumableResult + setUartSpeed(UartDividerRatio uartDividerRatio); + + /// Set the role of the radio in ranging operation + modm::ResumableResult + setRangingRole(RangingRole rangingRole); + + /// Enable advanced ranging + modm::ResumableResult + setAdvancedRanging(bool enable = true); + +public: + // The LSB of rfFrequency is equal to the PLL step i.e. 52e6/2^18 Hz, where 52e6 is the crystal frequency in Hz + static constexpr float frequencyLsb = float(52_MHz) / 262144.f; + +private: + ShortPreciseTimeout timeout; + uint8_t buffer[8]; +}; + +} + +#include "sx128x_impl.hpp" + +#endif \ No newline at end of file diff --git a/src/modm/driver/radio/sx128x.lb b/src/modm/driver/radio/sx128x.lb new file mode 100644 index 0000000000..f54c22ba18 --- /dev/null +++ b/src/modm/driver/radio/sx128x.lb @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen +# +# 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 = ":driver:sx128x" + module.description = """\ +Semtech SX1280/SX1281 Driver + +Long Range, Low Power, 2.4 GHz Transceiver with Ranging Capability + +!!! warning "The SX128x driver UART transport layer is untested" +""" + +def prepare(module, options): + module.depends( + ":architecture:gpio", + ":architecture:spi.device", + ":architecture:uart.device", + ":math:utils", + ":processing:resumable", + ":processing:timer") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/radio" + env.copy("sx128x.hpp") + env.copy("sx128x_impl.hpp") + env.copy("sx128x_definitions.hpp") + env.copy("sx128x_transport.hpp") + env.copy("sx128x_transport_impl.hpp") diff --git a/src/modm/driver/radio/sx128x_definitions.hpp b/src/modm/driver/radio/sx128x_definitions.hpp new file mode 100644 index 0000000000..2673d6a35e --- /dev/null +++ b/src/modm/driver/radio/sx128x_definitions.hpp @@ -0,0 +1,1002 @@ +/* + * Copyright (c) 2023, Lasse Alexander Jensen + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_SX128X_DEFINITIONS_HPP +#define MODM_SX128X_DEFINITIONS_HPP + +#include +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_sx128x +struct sx128x +{ + enum class + Opcode : uint8_t + { + // Status + GetStatus = 0xC0, + + // Register Access Operations + WriteRegister = 0x18, + ReadRegister = 0x19, + + // Data buffer Operations + WriteBuffer = 0x1A, + ReadBuffer = 0x1B, + + // Radio Operation Modes + SetSleep = 0x84, + SetStandby = 0x80, + SetFs = 0xC1, + SetTx = 0x83, + SetRx = 0x82, + SetRxDutyCycle = 0x94, + SetCad = 0xC5, + SetTxContinuousWave = 0xD1, + SetTxContinuousPreamble = 0xD2, + + // Radio Configuration + SetPacketType = 0x8A, + GetPacketType = 0x03, + SetRfFrequency = 0x86, + SetTxParams = 0x8E, + SetCadParams = 0x88, + SetBufferBaseAddress = 0x8F, + SetModulationParams = 0x8B, + SetPacketParams = 0x8C, + + // Communication Status Information + GetRxBufferStatus = 0x17, + GetPacketStatus = 0x1D, + GetRssiInst = 0x1F, + + // IRQ Handling + SetDioIrqParams = 0x8D, + GetIrqStatus = 0x15, + ClrIrqStatus = 0x97, + + // Settings functions + SetRegulatorMode = 0x96, + SetSaveContext = 0xD5, + SetAutoFs = 0x9E, + SetAutoTx = 0x98, + SetLongPreamble = 0x9B, + SetUartSpeed = 0x9D, + SetRangingRole = 0xA3, + SetAdvancedRanging = 0x9A, + }; + + enum class + Register : uint16_t + { + RxGain = 0x891, + ManualGainSetting = 0x895, + LnaGainValue = 0x89E, + LnaGainControl = 0x89F, + SynchPeakAttenuation = 0x8C2, + PayloadLength = 0x901, + LoRaHeaderMode = 0x903, + + RangingRequestAddressByte3 = 0x912, + RangingRequestAddressByte2 = 0x913, + RangingRequestAddressByte1 = 0x914, + RangingRequestAddressByte0 = 0x915, + + RangingDeviceAdressByte3 = 0x916, + RangingDeviceAdressByte2 = 0x917, + RangingDeviceAdressByte1 = 0x918, + RangingDeviceAdressByte0 = 0x919, + + RangingFilterWindowSize = 0x91E, + ResetRangingFilter = 0x923, + RangingResultMux = 0x924, + + SfAdditionalConfiguration = 0x925, + + RangingCalibrationByte2 = 0x92B, + RangingCalibrationByte1 = 0x92C, + RangingCalibrationByte0 = 0x92D, + + RangingIdCheckLength = 0x931, + FrequencyErrorCorrection = 0x93C, + + LoRaSynchWordByte1 = 0x944, + LoRaSynchWordByte0 = 0x945, + + FeiByte2 = 0x954, + FeiByte1 = 0x955, + FeiByte0 = 0x956, + + RangingResultByte2 = 0x961, + RangingResultByte1 = 0x962, + RangingResultByte0 = 0x963, + + RangingRssi = 0x964, + FreezeRangingResult = 0x97F, + PacketPreambleSettings = 0x9C1, + WhiteningInitialValue = 0x9C5, + + CrcPolynomialDefMsb = 0x9C6, + CrcPolynomialDefLsb = 0x9C7, + + CrcPolynomialSeedByte2 = 0x9C7, + CrcPolynomialSeedByte1 = 0x9C8, + CrcPolynomialSeedByte0 = 0x9C9, + + CrcMsbInitialValue = 0x9C8, + CrcLsbInitialValue = 0x9C9, + + SynchAddressControl = 0x9CD, + + SyncAddress1Byte4 = 0x9CE, + SyncAddress1Byte3 = 0x9CF, + SyncAddress1Byte2 = 0x9D0, + SyncAddress1Byte1 = 0x9D1, + SyncAddress1Byte0 = 0x9D2, + + SyncAddress2Byte4 = 0x9D3, + SyncAddress2Byte3 = 0x9D4, + SyncAddress2Byte2 = 0x9D5, + SyncAddress2Byte1 = 0x9D6, + SyncAddress2Byte0 = 0x9D7, + + SyncAddress3Byte4 = 0x9D8, + SyncAddress3Byte3 = 0x9D9, + SyncAddress3Byte2 = 0x9DA, + SyncAddress3Byte1 = 0x9DB, + SyncAddress3Byte0 = 0x9DC + }; + +public: + + struct modm_packed + Status + { + enum class + CircuitMode : uint8_t + { + StdbyRc = 0x2, + StdbyXosc = 0x3, + Fs = 0x4, + Rx = 0x5, + Tx = 0x6, + }; + + enum class + CommandStatus : uint8_t + { + Processed = 0x1, /// Transceiver has successfully processed the command + DataAvailable = 0x2, /// Data are available to host + Timeout = 0x3, /// Command time-out + ProcessingError = 0x4, /// Command processing error + ExecutionFailure = 0x5, /// Failure to execute command + TxDone = 0x6, /// Command Tx done + }; + + uint8_t: 2; + CommandStatus commandStatus: 3; + CircuitMode circuitMode: 3; + }; + + enum class + SleepConfig : uint8_t + { + DataRamFlushed = Bit0, + DataRamRetention = Bit1, + DataBufferRetention = Bit2, + }; + MODM_FLAGS8(SleepConfig); + + enum class + StandbyConfig : uint8_t + { + StdbyRc = 0, /// Device running on RC 13MHz, set STDBY_RC mode + StdbyXosc = 1, /// Device running on XTAL 52MHz, set STDBY_XOSC mode + }; + + enum class + PeriodBase : uint8_t + { + us15_625 = 0x00, + us62_5 = 0x01, + ms1 = 0x02, + ms4 = 0x03 + }; + + enum class + PacketType : uint8_t + { + Gfsk = 0x00, + LoRa = 0x01, + Ranging = 0x02, + Flrc = 0x03, + Ble = 0x04, + }; + + enum class + RampTime : uint8_t + { + us2 = 0x00, + us4 = 0x20, + us6 = 0x40, + us8 = 0x60, + us10 = 0x80, + us12 = 0xA0, + us16 = 0xC0, + us20 = 0xE0 + }; + + enum class + CadSymbolNumber : uint8_t + { + One = 0x00, + Two = 0x20, + Four = 0x40, + Eight = 0x60, + Sixteen = 0x80 + }; + + enum class + RegulatorMode : uint8_t + { + Ldo = 0x00, + DcDc = 0x01, + }; + + enum class + Irq : uint16_t + { + TxDone = Bit0, + RxDone = Bit1, + SyncWordValid = Bit2, + SyncWordError = Bit3, + HeaderValid = Bit4, + HeaderError = Bit5, + CrcError = Bit6, + RangingSlaveResponseDone = Bit7, + RangingSlaveRequestDiscard = Bit8, + RangingMasterResultValid = Bit9, + RangingMasterTimeout = Bit10, + RangingMasterRequestTimeout = Bit11, + CadDone = Bit12, + CadDetected = Bit13, + RxTxTimeout = Bit14, + PreambleDetected = Bit15, + }; + MODM_FLAGS16(Irq); + + struct RxBufferStatus + { + uint8_t rxPayloadLength; + uint8_t rxStartBufferPointer; + }; + + enum class + UartDividerRatio : uint16_t + { + Bd2400 = 0x00C2, + Bd4800 = 0x0183, + Bd9600 = 0x0306, + Bd14400 = 0x0489, + Bd19200 = 0x060D, + Bd38400 = 0x0C19, + Bd57600 = 0x1226, + Bd115200 = 0x244C, + Bd460600 = 0x9120, + Bd812490 = 0xFFFF, + }; + + enum class + RangingRole : uint8_t + { + Master = 0x01, + Slave = 0x00, + }; + + struct Gfsk + { + struct modm_packed + PacketStatus + { + enum class + Status : uint8_t + { + RxNoAck = Bit5, + PktSent = Bit0, + }; + MODM_FLAGS8(Status); + + enum class + Error : uint8_t + { + SyncError = Bit6, + LengthError = Bit5, + CrcError = Bit4, + AbortError = Bit3, + HeaderReceived = Bit2, + PacketCtrlBusy = Bit1, + }; + MODM_FLAGS8(Error); + + enum class + Sync : uint8_t + { + SyncAddrsCode2 = Bit2, + SyncAddrsCode1 = Bit1, + SyncAddrsCode0 = Bit0, + }; + MODM_FLAGS8(Sync); + + uint8_t rfu; + uint8_t rssiSync; + Error_t error; + Status_t status; + Sync_t sync; + }; + + struct modm_packed + PacketParams + { + enum class + PreambleLength : uint8_t + { + Bits4 = 0x00, + Bits8 = 0x10, + Bits12 = 0x20, + Bits16 = 0x30, + Bits20 = 0x40, + Bits24 = 0x50, + Bits28 = 0x60, + Bits32 = 0x70, + }; + + enum class + SyncWordLength : uint8_t + { + Byte1 = 0x00, + Byte2 = 0x02, + Byte3 = 0x04, + Byte4 = 0x06, + Byte5 = 0x08, + }; + + enum class + SyncWordMatch : uint8_t + { + Disable = 0x00, + One = 0x10, + Two = 0x20, + OneTwo = 0x30, + Three = 0x40, + OneThree = 0x50, + TwoThree = 0x60, + OneTwoThree = 0x70, + }; + + enum class + HeaderType : uint8_t + { + FixedLength = 0x00, + VariableLength = 0x20, + }; + + enum class + CrcLength : uint8_t + { + Off = 0x00, + Byte2 = 0x10, + Byte3 = 0x20, + Byte4 = 0x30, + }; + + enum class + Whitening : uint8_t + { + Enable = 0x00, + Disable = 0x08, + }; + + PreambleLength preambleLength; + SyncWordLength syncWordLength; + SyncWordMatch syncWordMatch; + HeaderType headerType; + uint8_t payloadLength; + CrcLength crcLength; + Whitening whitening; + }; + + struct modm_packed + ModulationParams + { + enum class + BitrateBandwidth : uint8_t + { + Br2000Bw2400 = 0x04, + Br1600Bw2400 = 0x28, + Br1000Bw2400 = 0x4C, + Br1000Bw1200 = 0x45, + Br800Bw2400 = 0x70, + Br800Bw1200 = 0x69, + Br500Bw1200 = 0x8D, + Br500Bw600 = 0x86, + Br400Bw1200 = 0xB1, + Br400Bw600 = 0xAA, + Br250Bw600 = 0xCE, + Br250Bw300 = 0xC7, + Br124Bw300 = 0xEF, + }; + + enum class + ModulationIndex : uint8_t + { + Ind0_35 = 0x00, + Ind0_50 = 0x01, + Ind0_75 = 0x02, + Ind1_00 = 0x03, + Ind1_25 = 0x04, + Ind1_50 = 0x05, + Ind1_75 = 0x06, + Ind2_00 = 0x07, + Ind2_25 = 0x08, + Ind2_50 = 0x09, + Ind2_75 = 0x0A, + Ind3_00 = 0x0B, + Ind3_25 = 0x0C, + Ind3_50 = 0x0D, + Ind3_75 = 0x0E, + Ind4_00 = 0x0F, + }; + + enum class + ModulationShaping : uint8_t + { + BtOff = 0x00, + Bt1_0 = 0x10, + Bt0_5 = 0x20, + }; + + BitrateBandwidth bitrateBandwidth; + ModulationIndex modulationIndex; + ModulationShaping modulationShaping; + }; + }; + + struct Ble + { + struct modm_packed + PacketStatus + { + enum class + Status : uint8_t + { + RxNoAck = Bit5, + PktSent = Bit0, + }; + MODM_FLAGS8(Status); + + enum class + Error : uint8_t + { + SyncError = Bit6, + LengthError = Bit5, + CrcError = Bit4, + AbortError = Bit3, + HeaderReceived = Bit2, + PacketCtrlBusy = Bit1, + }; + MODM_FLAGS8(Error); + + enum class + Sync : uint8_t + { + SyncAddrsCode2 = Bit2, + SyncAddrsCode1 = Bit1, + SyncAddrsCode0 = Bit0, + }; + MODM_FLAGS8(Sync); + + uint8_t rfu; + uint8_t rssiSync; + Error_t error; + Status_t status; + Sync_t sync; + }; + + struct modm_packed + PacketParams + { + enum class + ConnectionState : uint8_t + { + PayloadLengthMax31Bytes = 0x00, + PayloadLengthMax37Bytes = 0x20, + TxTestMode = 0x40, + PayloadLengthMax355Bytes = 0x80, + }; + + enum class + CrcLength : uint8_t + { + Off = 0x00, + Byte3 = 0x10, + }; + + enum class + TestPayload : uint8_t + { + Prbs9 = 0x00, + Eyelong10 = 0x04, + Eyeshort10 = 0x08, + Prbs15 = 0x0C, + All1 = 0x10, + All0 = 0x14, + Eyelong01 = 0x18, + Eyeshort01 = 0x1c, + }; + + enum class + Whitening : uint8_t + { + Enable = 0x00, + Disable = 0x08, + }; + + ConnectionState connectionState; + CrcLength crcLength; + TestPayload testPayload; + Whitening whitening; + }; + + struct modm_packed + ModulationParams + { + enum class + BitrateBandwidth : uint8_t + { + Br2000Bw2400 = 0x04, + Br1600Bw2400 = 0x28, + Br1000Bw2400 = 0x4C, + Br1000Bw1200 = 0x45, + Br800Bw2400 = 0x70, + Br800Bw1200 = 0x69, + Br500Bw1200 = 0x8D, + Br500Bw600 = 0x86, + Br400Bw1200 = 0xB1, + Br400Bw600 = 0xAA, + Br250Bw600 = 0xCE, + Br250Bw300 = 0xC7, + Br124Bw300 = 0xEF, + }; + + enum class + ModulationIndex : uint8_t + { + Ind0_35 = 0x00, + Ind0_50 = 0x01, + Ind0_75 = 0x02, + Ind1_00 = 0x03, + Ind1_25 = 0x04, + Ind1_50 = 0x05, + Ind1_75 = 0x06, + Ind2_00 = 0x07, + Ind2_25 = 0x08, + Ind2_50 = 0x09, + Ind2_75 = 0x0A, + Ind3_00 = 0x0B, + Ind3_25 = 0x0C, + Ind3_50 = 0x0D, + Ind3_75 = 0x0E, + Ind4_00 = 0x0F, + }; + + enum class + ModulationShaping : uint8_t + { + BtOff = 0x00, + Bt1_0 = 0x10, + Bt0_5 = 0x20, + }; + + BitrateBandwidth bitrateBandwidth; + ModulationIndex modulationIndex; + ModulationShaping modulationShaping; + }; + }; + + struct Flrc + { + struct modm_packed + PacketStatus + { + enum class + Status : uint8_t + { + RxNoAck = Bit5, + PktSent = Bit0, + }; + MODM_FLAGS8(Status); + + enum class + Error : uint8_t + { + SyncError = Bit6, + LengthError = Bit5, + CrcError = Bit4, + AbortError = Bit3, + HeaderReceived = Bit2, + PacketCtrlBusy = Bit1, + }; + MODM_FLAGS8(Error); + + enum class + Sync : uint8_t + { + SyncAddrsCode2 = Bit2, + SyncAddrsCode1 = Bit1, + SyncAddrsCode0 = Bit0, + }; + MODM_FLAGS8(Sync); + + uint8_t rfu; + uint8_t rssiSync; + Error_t error; + Status_t status; + Sync_t sync; + }; + + struct modm_packed + PacketParams + { + enum class + PreambleLength : uint8_t + { + Bits4 = 0x00, + Bits8 = 0x10, + Bits12 = 0x20, + Bits16 = 0x30, + Bits20 = 0x40, + Bits24 = 0x50, + Bits28 = 0x60, + Bits32 = 0x70, + }; + + enum class + SyncWordLength : uint8_t + { + NoSync = 0x00, + Bits32 = 0x02, + }; + + enum class + SyncWordMatch : uint8_t + { + Disable = 0x00, + One = 0x10, + Two = 0x20, + OneTwo = 0x30, + Three = 0x40, + OneThree = 0x50, + TwoThree = 0x60, + OneTwoThree = 0x70, + }; + + enum class + PacketType : uint8_t + { + FixedLength = 0x00, + VariableLength = 0x20, + }; + + enum class + CrcLength : uint8_t + { + Off = 0x00, + Byte1 = 0x10, + Byte2 = 0x20, + }; + + enum class + Whitening : uint8_t + { + Disable = 0x08, + }; + + PreambleLength preambleLength; + SyncWordLength syncWordLength; + SyncWordMatch syncWordMatch; + PacketType packetType; + uint8_t payloadLength; + CrcLength crcLength; + Whitening whitening; + }; + + struct modm_packed + ModulationParams + { + enum class + BitrateBandwidth : uint8_t + { + Br1300Bw1200 = 0x45, + Br1000Bw1200 = 0x69, + Br650Bw600 = 0x86, + Br520Bw600 = 0xAA, + Br325Bw300 = 0xC7, + Br260Bw300 = 0xEB, + }; + + enum class + CodingRate : uint8_t + { + Cr1_2 = 0x00, + Cr3_4 = 0x02, + Cr1_1 = 0x04, + }; + + enum class + ModulationShaping : uint8_t + { + BtDis = 0x00, + Bt1_0 = 0x10, + Bt0_5 = 0x20, + }; + + BitrateBandwidth bitrateBandwidth; + CodingRate codingRate; + ModulationShaping modulationShaping; + }; + }; + + struct LoRa + { + struct modm_packed + PacketStatus + { + uint8_t rssiSync; + uint8_t snr; + }; + + struct modm_packed + PacketParams + { + enum class + HeaderType : uint8_t + { + Explicit = 0x00, + Implicit = 0x80, + }; + + enum class + Crc : uint8_t + { + Enable = 0x20, + Disable = 0x00, + }; + + enum class + InvertIq : uint8_t + { + Inverted = 0x00, + Standard = 0x40, + }; + + uint8_t preambleLength; + HeaderType headerType; + uint8_t payloadLength; + Crc crc; + InvertIq invertIq; + }; + + struct modm_packed + ModulationParams + { + enum class + SpreadingFactor : uint8_t + { + Sf5 = 0x50, + Sf6 = 0x60, + Sf7 = 0x70, + Sf8 = 0x80, + Sf9 = 0x90, + Sf10 = 0xA0, + Sf11 = 0xB0, + Sf12 = 0xC0, + }; + + enum class + Bandwidth : uint8_t + { + Bw1600 = 0x0A, + Bw800 = 0x18, + Bw400 = 0x26, + Bw200 = 0x34, + }; + + enum class + CodingRate : uint8_t + { + Cr4_5 = 0x01, + Cr4_6 = 0x02, + Cr4_7 = 0x03, + Cr4_8 = 0x04, + Cr_Li_4_5 = 0x05, + Cr_Li_4_6 = 0x06, + Cr_Li_4_7 = 0x07, + }; + + SpreadingFactor spreadingFactor; + Bandwidth bandwidth; + CodingRate codingRate; + }; + }; + + struct Ranging + { + struct modm_packed + PacketStatus + { + uint8_t rssiSync; + uint8_t sni; + }; + + struct modm_packed + PacketParams + { + enum class + HeaderType : uint8_t + { + Explicit = 0x00, + Implicit = 0x80, + }; + + enum class + Crc : uint8_t + { + Enable = 0x20, + Disable = 0x00, + }; + + enum class + InvertIq : uint8_t + { + Inverted = 0x00, + Standard = 0x40, + }; + + uint8_t preambleLength; + HeaderType headerType; + uint8_t payloadLength; + Crc crc; + InvertIq invertIq; + }; + + struct modm_packed + ModulationParams + { + enum class + SpreadingFactor : uint8_t + { + Sf5 = 0x50, + Sf6 = 0x60, + Sf7 = 0x70, + Sf8 = 0x80, + Sf9 = 0x90, + Sf10 = 0xA0, + }; + + enum class + Bandwidth : uint8_t + { + Bw1600 = 0x0A, + Bw800 = 0x18, + Bw400 = 0x26, + }; + + enum class + CodingRate : uint8_t + { + Cr4_5 = 0x01, + Cr4_6 = 0x02, + Cr4_7 = 0x03, + Cr4_8 = 0x04, + Cr_Li_4_5 = 0x05, + Cr_Li_4_6 = 0x06, + Cr_Li_4_7 = 0x07, + }; + + SpreadingFactor spreadingFactor; + Bandwidth bandwidth; + CodingRate codingRate; + }; + }; + + union PacketParams + { + Gfsk::PacketParams gfsk; + Flrc::PacketParams flrc; + Ble::PacketParams ble; + LoRa::PacketParams lora; + Ranging::PacketParams ranging; + + PacketParams() {} + PacketParams(Gfsk::PacketParams packetParams) : gfsk(packetParams) {} + PacketParams(Flrc::PacketParams packetParams) : flrc(packetParams) {} + PacketParams(Ble::PacketParams packetParams) : ble(packetParams) {} + PacketParams(LoRa::PacketParams packetParams) : lora(packetParams) {} + PacketParams(Ranging::PacketParams packetParams) : ranging(packetParams) {} + }; + + union PacketStatus + { + Gfsk::PacketStatus gfsk; + Flrc::PacketStatus flrc; + Ble::PacketStatus ble; + LoRa::PacketStatus lora; + Ranging::PacketStatus ranging; + + PacketStatus() {} + PacketStatus(Gfsk::PacketStatus packetStatus) : gfsk(packetStatus) {} + PacketStatus(Flrc::PacketStatus packetStatus) : flrc(packetStatus) {} + PacketStatus(Ble::PacketStatus packetStatus) : ble(packetStatus) {} + PacketStatus(LoRa::PacketStatus packetStatus) : lora(packetStatus) {} + PacketStatus(Ranging::PacketStatus packetStatus) : ranging(packetStatus) {} + }; + + union ModulationParams + { + Gfsk::ModulationParams gfsk; + Flrc::ModulationParams flrc; + Ble::ModulationParams ble; + LoRa::ModulationParams lora; + Ranging::ModulationParams ranging; + + ModulationParams() {} + ModulationParams(Gfsk::ModulationParams modulationParams) : gfsk(modulationParams) {} + ModulationParams(Flrc::ModulationParams modulationParams) : flrc(modulationParams) {} + ModulationParams(Ble::ModulationParams modulationParams) : ble(modulationParams) {} + ModulationParams(LoRa::ModulationParams modulationParams) : lora(modulationParams) {} + ModulationParams(Ranging::ModulationParams modulationParams) : ranging(modulationParams) {} + }; + +protected: + /// @cond + static constexpr uint16_t + i(Register reg) { return uint16_t(reg); } + static constexpr uint8_t + i(Opcode opcode) { return uint8_t(opcode); } + static constexpr uint8_t + i(StandbyConfig standbyConfig) { return uint8_t(standbyConfig); } + static constexpr uint8_t + i(PeriodBase periodBase) { return uint8_t(periodBase); } + static constexpr uint8_t + i(PacketType packetType) { return uint8_t(packetType); } + static constexpr uint8_t + i(RampTime rampTime) { return uint8_t(rampTime); } + static constexpr uint8_t + i(CadSymbolNumber cadSymbolNumber) { return uint8_t(cadSymbolNumber); } + static constexpr uint8_t + i(RegulatorMode regulatorMode) { return uint8_t(regulatorMode); } + static constexpr uint16_t + i(UartDividerRatio uartDividerRatio) { return uint16_t(uartDividerRatio); } + static constexpr uint8_t + i(RangingRole rangingRole) { return uint8_t(rangingRole); } + /// @endcond +}; + +} + + + +#endif \ No newline at end of file diff --git a/src/modm/driver/radio/sx128x_impl.hpp b/src/modm/driver/radio/sx128x_impl.hpp new file mode 100644 index 0000000000..251041eea8 --- /dev/null +++ b/src/modm/driver/radio/sx128x_impl.hpp @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2023, Lasse Alexander Jensen + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_SX128X_HPP +# error "Don't include this file directly, use 'sx128x.hpp' instead!" +#endif + +#include + +namespace modm +{ + +// ---------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +bool +Sx128x< Transport, Reset, Busy >::isBusy() +{ + return Busy::read(); +} + +// ---------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::reset() +{ + RF_BEGIN(); + + Reset::setOutput(modm::Gpio::Low); + timeout.restart(std::chrono::milliseconds(50)); + RF_WAIT_UNTIL(timeout.isExpired()); + + Reset::setOutput(modm::Gpio::High); + timeout.restart(std::chrono::milliseconds(20)); + RF_WAIT_UNTIL(timeout.isExpired()); + + RF_END(); +} + +// ---------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getStatus(sx128x::Status *status) +{ + // The GetStatus command can be issued at any time + return this->writeCommandSingleData(Opcode::GetStatus, std::span{status, 1}); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::writeRegister(Register reg, uint8_t data) +{ + buffer[0] = data; + return this->writeRegister(reg, std::span{buffer, 1}); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::writeRegister(Register reg, std::span data) +{ + RF_BEGIN(); + + buffer[0] = (i(reg) >> 8) & 0xFF; + buffer[1] = i(reg) & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Command(Opcode::WriteRegister, std::span{buffer, 2}), data)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::readRegister(Register reg, uint8_t *value) +{ + return this->readRegister(reg, std::span{value, 1}); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::readRegister(Register reg, std::span data) +{ + RF_BEGIN(); + + buffer[0] = (i(reg) >> 8) & 0xFF; + buffer[1] = i(reg) & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->readCommand(Command(Opcode::ReadRegister, std::span{buffer, 2}), data)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::writeBuffer(uint8_t offset, std::span data) +{ + RF_BEGIN(); + + buffer[0] = offset; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Command(Opcode::WriteBuffer, std::span{buffer, 1}), data)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::readBuffer(uint8_t offset, std::span data) +{ + RF_BEGIN(); + + buffer[0] = offset; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->readCommand(Command(Opcode::ReadBuffer, std::span{buffer, 1}), data)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setSleep(SleepConfig_t sleepConfig) +{ + RF_BEGIN(); + + buffer[0] = sleepConfig.value; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetSleep, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setStandby(StandbyConfig standbyConfig) +{ + RF_BEGIN(); + + buffer[0] = i(standbyConfig); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetStandby, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setFs() +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommandSingleData(Opcode::SetFs)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setTx(PeriodBase periodBase, uint16_t periodBaseCount) +{ + RF_BEGIN(); + + buffer[0] = i(periodBase); + buffer[1] = (periodBaseCount >> 8) & 0xFF; + buffer[2] = periodBaseCount & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetTx, std::span{buffer, 3})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setRx(PeriodBase periodBase, uint16_t periodBaseCount) +{ + RF_BEGIN(); + + buffer[0] = i(periodBase); + buffer[1] = (periodBaseCount >> 8) & 0xFF; + buffer[2] = periodBaseCount & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetRx, std::span{buffer, 3}) ); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setRxDutyCycle(PeriodBase periodBase, uint16_t rxPeriodBaseCount, uint16_t sleepPeriodBaseCount) +{ + RF_BEGIN(); + + buffer[0] = i(periodBase); + buffer[1] = (rxPeriodBaseCount >> 8) & 0xFF; + buffer[2] = rxPeriodBaseCount & 0xFF; + buffer[3] = (sleepPeriodBaseCount >> 8) & 0xFF; + buffer[4] = sleepPeriodBaseCount & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetRxDutyCycle, std::span{buffer, 5})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setCad() +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommandSingleData(Opcode::SetCad)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setTxContinuousWave() +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommandSingleData(Opcode::SetTxContinuousWave)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setTxContinuousPreamble() +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommandSingleData(Opcode::SetTxContinuousPreamble)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setPacketType(PacketType packetType) +{ + RF_BEGIN(); + + buffer[0] = i(packetType); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetPacketType, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getPacketType(PacketType *packetType) +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + if (RF_CALL(this->readCommand(Opcode::GetPacketType, std::span{(uint8_t *) packetType, 1}))) + { + *packetType = PacketType(buffer[0]); + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setRfFrequency(uint32_t rfFrequency) +{ + RF_BEGIN(); + + buffer[0] = (rfFrequency >> 16) & 0xFF; + buffer[1] = (rfFrequency >> 8) & 0xFF; + buffer[2] = rfFrequency & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetRfFrequency, std::span{buffer, 3})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setTxParams(uint8_t power, RampTime rampTime) +{ + RF_BEGIN(); + + buffer[0] = power; + buffer[1] = i(rampTime); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetTxParams, std::span{buffer, 2})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setCadParams(CadSymbolNumber cadSymbolNumber) +{ + RF_BEGIN(); + + buffer[0] = uint8_t(cadSymbolNumber); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetCadParams, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setBufferBaseAddress(uint8_t txBaseAddress, uint8_t rxBaseAddress) +{ + RF_BEGIN(); + + buffer[0] = txBaseAddress; + buffer[1] = rxBaseAddress; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetBufferBaseAddress, std::span{buffer, 2})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setModulationParams(ModulationParams modulationParams) +{ + RF_BEGIN(); + + std::memcpy(buffer, (uint8_t *) &modulationParams, 3); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetModulationParams, std::span{buffer, 3})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setPacketParams(PacketParams packetParams) +{ + RF_BEGIN(); + + std::memcpy(buffer, (uint8_t *) &packetParams, 7); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetPacketParams, std::span{buffer, 7})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getRxBufferStatus(RxBufferStatus *rxBufferStatus) +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + if (RF_CALL(this->readCommand(Opcode::GetRxBufferStatus, std::span{buffer, 2}))) + { + std::memcpy((uint8_t *) rxBufferStatus, buffer, 2); + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getPacketStatus(PacketStatus *packetStatus) +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + if (RF_CALL(this->readCommand(Opcode::GetPacketStatus, std::span{buffer, 5}))) + { + std::memcpy((uint8_t *) packetStatus, buffer, 5); + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getRssiInst(uint8_t *rssiInst) +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + if (RF_CALL(this->readCommand(Opcode::GetRssiInst, std::span{buffer, 1}))) + { + *rssiInst = buffer[0]; + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setDioIrqParams(Irq_t irqMask, Irq_t dio1Mask, Irq_t dio2Mask, Irq_t dio3Mask) +{ + RF_BEGIN(); + + buffer[0] = (irqMask.value >> 8) & 0xFF; + buffer[1] = irqMask.value & 0xFF; + buffer[2] = (dio1Mask.value >> 8) & 0xFF; + buffer[3] = dio1Mask.value & 0xFF; + buffer[4] = (dio2Mask.value >> 8) & 0xFF; + buffer[5] = dio2Mask.value & 0xFF; + buffer[6] = (dio3Mask.value >> 8) & 0xFF; + buffer[7] = dio3Mask.value & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetDioIrqParams, std::span{buffer, 8})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::getIrqStatus(Irq_t *irqStatus) +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + if (RF_CALL(this->readCommand(Opcode::GetIrqStatus, std::span{buffer, 2}))) + { + *irqStatus = Irq_t((buffer[0] << 8) | buffer[1]); + RF_RETURN(true); + } + + RF_END_RETURN(false); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::clearIrqStatus(Irq_t irqMask) +{ + RF_BEGIN(); + + buffer[0] = (irqMask.value >> 8) & 0xFF; + buffer[1] = irqMask.value & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::ClrIrqStatus, std::span{buffer, 2})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setRegulatorMode(RegulatorMode regModeParam) +{ + RF_BEGIN(); + + buffer[0] = i(regModeParam); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetRegulatorMode, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setSaveContext() +{ + RF_BEGIN(); + + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommandSingleData(Opcode::SetSaveContext)); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setAutoFs(bool enable) +{ + RF_BEGIN(); + + buffer[0] = enable ? 0x01 : 0x00; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetAutoFs, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setAutoTx(uint16_t time) +{ + RF_BEGIN(); + + buffer[0] = (time >> 8) & 0xFF; + buffer[1] = time & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetAutoTx, std::span{buffer, 2})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setLongPreamble(bool enable) +{ + RF_BEGIN(); + + buffer[0] = enable ? 0x01 : 0x00; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetLongPreamble, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setUartSpeed(UartDividerRatio uartDividerRatio) +{ + RF_BEGIN(); + + buffer[0] = (i(uartDividerRatio) >> 8) & 0xFF; + buffer[1] = i(uartDividerRatio) & 0xFF; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetLongPreamble, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setRangingRole(RangingRole rangingRole) +{ + RF_BEGIN(); + + buffer[0] = i(rangingRole); + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetLongPreamble, std::span{buffer, 1})); +} + +// ----------------------------------------------------------------------------- + +template < class Transport, class Reset, class Busy > +modm::ResumableResult +Sx128x< Transport, Reset, Busy >::setAdvancedRanging(bool enable) +{ + RF_BEGIN(); + + buffer[0] = enable ? 0x01 : 0x00; + RF_WAIT_WHILE(isBusy()); + + RF_END_RETURN_CALL(this->writeCommand(Opcode::SetAdvancedRanging, std::span{buffer, 1})); +} + +} \ No newline at end of file diff --git a/src/modm/driver/radio/sx128x_transport.hpp b/src/modm/driver/radio/sx128x_transport.hpp new file mode 100644 index 0000000000..8cc98bcf2c --- /dev/null +++ b/src/modm/driver/radio/sx128x_transport.hpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_SX128X_TRANSPORT_HPP +#define MODM_SX128X_TRANSPORT_HPP + +#include "sx128x_definitions.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace modm +{ + +/** + * SX128X Transport Layer Interface + * + * @ingroup modm_driver_sx128x_transport + * @author Rasmus Kleist Hørlyck Sørensen + */ +class Sx128xTransport +{ +public: + struct Command : sx128x + { + Command(Opcode opcode) : opcode(opcode) {} + Command(Opcode opcode, std::span vargs) : opcode(opcode), vargs(vargs) {} + + // DATA ACCESS + ///@{ + /// + Opcode inline + getOpcode() const + { return opcode; } + + void inline + getOpcode(Opcode *opcode) const + { *opcode = getOpcode(); } + + void inline + getOpcode(uint8_t *opcode) const + { *opcode = i(getOpcode()); } + + std::span inline + getVargs() const + { return vargs; } + ///@} + + bool inline + hasVargs() const + { return !vargs.empty(); } + + std::size_t inline + getVargsCount() const + { return vargs.size(); } + + private: + Opcode opcode; + std::span vargs; + }; +}; + +/** + * SX128X SPI Transport Layer. + * + * @tparam SpiMaster SpiMaster interface + * @tparam Cs Chip-select pin + * + * @ingroup modm_driver_sx128x_transport + * @author Rasmus Kleist Hørlyck Sørensen + */ +template < class SpiMaster, class Cs > +class Sx128xTransportSpi : public Sx128xTransport, public SpiDevice< SpiMaster >, protected NestedResumable<2> +{ +public: + Sx128xTransportSpi() = default; + +protected: + modm::ResumableResult + writeCommandSingleData(Command command, uint8_t *data = nullptr); + + modm::ResumableResult + writeCommand(Command command, std::span data); + + modm::ResumableResult + readCommand(Command command, std::span data); + +private: + uint8_t commandBuffer[4]; +}; + +/** + * SX128X UART Transport Layer. + * + * @tparam Uart UART interface + * + * @ingroup modm_driver_sx128x_transport + * @author Rasmus Kleist Hørlyck Sørensen + */ +template < class Uart > +class Sx128xTransportUart : public Sx128xTransport, public UartDevice< Uart, 2 > +{ +public: + Sx128xTransportUart() = default; + +protected: + modm::ResumableResult + writeCommandSingleData(Command command, uint8_t *data = nullptr); + + modm::ResumableResult + writeCommand(Command command, std::span data); + + modm::ResumableResult + readCommand(Command command, std::span data); + +private: + uint8_t commandBuffer[4]; +}; + +} // namespace modm + +#include "sx128x_transport_impl.hpp" + +#endif // MODM_SX128X_TRANSPORT_HPP diff --git a/src/modm/driver/radio/sx128x_transport_impl.hpp b/src/modm/driver/radio/sx128x_transport_impl.hpp new file mode 100644 index 0000000000..86967b1f46 --- /dev/null +++ b/src/modm/driver/radio/sx128x_transport_impl.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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_SX128X_TRANSPORT_HPP +# error "Don't include this file directly, use 'sx128x_transport.hpp' instead!" +#endif + +#include + +namespace modm +{ + +template < class SpiMaster, class Cs > +modm::ResumableResult +Sx128xTransportSpi::writeCommandSingleData(Command command, uint8_t *data) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + command.getOpcode(commandBuffer); + RF_CALL(SpiMaster::transfer(commandBuffer, data, 1)); + + if (this->releaseMaster()) + Cs::set(); + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +Sx128xTransportSpi::writeCommand(Command command, std::span data) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + command.getOpcode(commandBuffer); + if (command.hasVargs()) { + auto vargs = command.getVargs(); + std::memcpy(commandBuffer + 1, vargs.data(), vargs.size()); + } + + RF_CALL(SpiMaster::transfer(commandBuffer, nullptr, command.getVargsCount() + 1)); + RF_CALL(SpiMaster::transfer(&data[0], nullptr, data.size())); + + if (this->releaseMaster()) + Cs::set(); + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template < class SpiMaster, class Cs > +modm::ResumableResult +Sx128xTransportSpi::readCommand(Command command, std::span data) +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + Cs::reset(); + + command.getOpcode(commandBuffer); + if (command.hasVargs()) { + auto vargs = command.getVargs(); + std::memcpy(commandBuffer + 1, vargs.data(), vargs.size()); + commandBuffer[1 + vargs.size()] = 0x00; + } else { + commandBuffer[1] = 0x00; + } + + RF_CALL(SpiMaster::transfer(commandBuffer, nullptr, command.getVargsCount() + 2)); + RF_CALL(SpiMaster::transfer(nullptr, &data[0], data.size())); + + if (this->releaseMaster()) + Cs::set(); + + RF_END_RETURN(true); +} + +// ---------------------------------------------------------------------------- + +template < class Uart > +modm::ResumableResult +Sx128xTransportUart::writeCommandSingleData(Command command, uint8_t *data) +{ + RF_BEGIN(); + + command.getOpcode(commandBuffer); + if (RF_CALL(this->write(commandBuffer[0]))) { + if (data != nullptr) { + RF_RETURN_CALL(this->read(data)); + } else { + RF_RETURN(true); + } + } + + RF_END_RETURN(false); +} + +// ---------------------------------------------------------------------------- + +template < class Uart > +modm::ResumableResult +Sx128xTransportUart::writeCommand(Command command, std::span data) +{ + RF_BEGIN(); + + command.getOpcode(commandBuffer); + if (command.hasVargs()) { + auto vargs = command.getVargs(); + std::memcpy(commandBuffer + 1, vargs.data(), vargs.size()); + commandBuffer[1 + vargs.size()] = data.size(); + } else { + commandBuffer[1] = data.size(); + } + + if (RF_CALL(this->write(commandBuffer, 2 + command.getVargsCount()))) { + RF_RETURN_CALL(this->write(&data[0], data.size())); + } + + RF_END_RETURN(false); +} + +// ---------------------------------------------------------------------------- + +template < class Uart > +modm::ResumableResult +Sx128xTransportUart::readCommand(Command command, std::span data) +{ + RF_BEGIN(); + + command.getOpcode(commandBuffer); + if (command.hasVargs()) { + auto vargs = command.getVargs(); + std::memcpy(commandBuffer + 1, vargs.data(), vargs.size()); + commandBuffer[1 + vargs.size()] = data.size(); + } else { + commandBuffer[1] = data.size(); + } + + if (RF_CALL(this->write(commandBuffer, 2 + command.getVargsCount()))) { + RF_RETURN_CALL(this->read(&data[0], data.size())); + } + + RF_END_RETURN(false); +} + +} // namespace modm \ No newline at end of file From 517bd845748d24f512c3159f9089e9e19b4c9fda Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Mon, 7 Aug 2023 20:15:01 +0200 Subject: [PATCH 047/159] [example] Adding SX128x LoRa example --- examples/nucleo_g474re/sx128x_lora/main.cpp | 278 ++++++++++++++++++ .../nucleo_g474re/sx128x_lora/project.xml | 15 + 2 files changed, 293 insertions(+) create mode 100644 examples/nucleo_g474re/sx128x_lora/main.cpp create mode 100644 examples/nucleo_g474re/sx128x_lora/project.xml diff --git a/examples/nucleo_g474re/sx128x_lora/main.cpp b/examples/nucleo_g474re/sx128x_lora/main.cpp new file mode 100644 index 0000000000..54b0282ed2 --- /dev/null +++ b/examples/nucleo_g474re/sx128x_lora/main.cpp @@ -0,0 +1,278 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 +#include +#include + +using Sck = GpioA5; +using Miso = GpioA6; +using Mosi = GpioA7; +using SpiMaster = modm::platform::SpiMaster1; + +namespace +{ + +namespace rx +{ + std::atomic_bool dio1 = false; + std::atomic_bool dio2 = false; + std::atomic_bool dio3 = false; +} + +namespace tx +{ + std::atomic_bool dio1 = false; + std::atomic_bool dio2 = false; + std::atomic_bool dio3 = false; +} + +static constexpr modm::sx128x::LoRa::ModulationParams modulationParams = { + .spreadingFactor = modm::sx128x::LoRa::ModulationParams::SpreadingFactor::Sf9, + .bandwidth = modm::sx128x::LoRa::ModulationParams::Bandwidth::Bw400, + .codingRate = modm::sx128x::LoRa::ModulationParams::CodingRate::Cr_Li_4_7 +}; + +static constexpr modm::sx128x::LoRa::PacketParams packetParams = { + .preambleLength = 12, + .headerType = modm::sx128x::LoRa::PacketParams::HeaderType::Explicit, + .payloadLength = 4, + .crc = modm::sx128x::LoRa::PacketParams::Crc::Enable, + .invertIq = modm::sx128x::LoRa::PacketParams::InvertIq::Standard +}; + +} + +class RxThread : public modm::sx128x, public modm::pt::Protothread +{ + using Reset = GpioB3; + using Busy = GpioB4; + using Dio1 = GpioB5; + using Dio2 = GpioB6; + using Dio3 = GpioB7; + + using Nss = GpioD2; + using Transport = modm::Sx128xTransportSpi; + +public: + RxThread() {} + + inline bool + run() + { + PT_BEGIN(); + + Nss::setOutput(modm::Gpio::High); + Reset::setOutput(modm::Gpio::Low); + Busy::setInput(modm::platform::Gpio::InputType::PullDown); + + Dio1::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { rx::dio1 = true; }); + + Dio2::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { rx::dio2 = true; }); + + Dio3::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { rx::dio3 = true; }); + + PT_CALL(radio.reset()); + PT_CALL(radio.setStandby()); + + // Initialize the sx128x + PT_CALL(radio.setPacketType(PacketType::LoRa)); + PT_CALL(radio.setRfFrequency(2457_MHz / radio.frequencyLsb)); + PT_CALL(radio.setRegulatorMode(RegulatorMode::Ldo)); + PT_CALL(radio.setBufferBaseAddress(0, 0)); + PT_CALL(radio.setModulationParams(modulationParams)); + PT_CALL(radio.writeRegister(Register::SfAdditionalConfiguration, 0x32)); + PT_CALL(radio.writeRegister(Register::FrequencyErrorCorrection, 0x01)); + PT_CALL(radio.setPacketParams(packetParams)); + PT_CALL(radio.setDioIrqParams(Irq::RxDone | Irq::RxTxTimeout, Irq::RxDone, Irq::RxTxTimeout)); + PT_CALL(radio.setRx(sx128x::PeriodBase::ms1, 1000)); + + MODM_LOG_DEBUG << "Sx128x initialization complete!" << modm::endl; + + while (true) + { + if (rx::dio1.exchange(false)) + { + PT_CALL(radio.getIrqStatus(&irqStatus)); + if (irqStatus.any(Irq::RxDone)) + { + PT_CALL(radio.clearIrqStatus(Irq::RxDone | Irq::RxTxTimeout)); + PT_CALL(radio.setRx(sx128x::PeriodBase::ms1, 1000)); + + PT_CALL(radio.getRxBufferStatus(&rxBufferStatus)); + PT_CALL(radio.getPacketStatus(&packetStatus)); + PT_CALL(radio.readBuffer(rxBufferStatus.rxStartBufferPointer, std::span{buffer, rxBufferStatus.rxPayloadLength})); + + if (rxBufferStatus.rxPayloadLength > 0) + { + uint32_t counter; + std::memcpy((uint8_t *) &counter, buffer, sizeof(counter)); + MODM_LOG_DEBUG << "Received Message" << modm::endl; + MODM_LOG_DEBUG << "Counter: " << counter << modm::endl; + } + } + } + + if (rx::dio2.exchange(false)) + { + PT_CALL(radio.getIrqStatus(&irqStatus)); + if (irqStatus.any(Irq::RxTxTimeout)) + { + // RxTxTimeout Interrupt received! + // Clear irq and set to rx again. + PT_CALL(radio.clearIrqStatus(Irq::RxTxTimeout)); + PT_CALL(radio.setRx(sx128x::PeriodBase::ms1, 1000)); + MODM_LOG_DEBUG << "RxTxTimeout Interrupt!" << modm::endl; + } + } + PT_YIELD(); + } + PT_END(); + } + +private: + Irq_t irqStatus; + PacketStatus packetStatus; + RxBufferStatus rxBufferStatus; + + modm::Sx128x< Transport, Reset, Busy > radio; + +private: + static constexpr size_t bufferSize = 256; + uint8_t buffer[bufferSize]; +} rxThread; + +class TxThread : public modm::sx128x, public modm::pt::Protothread +{ + using Reset = modm::platform::GpioC2; + using Busy = modm::platform::GpioC3; + using Dio1 = modm::platform::GpioA0; + using Dio2 = modm::platform::GpioA1; + using Dio3 = modm::platform::GpioA2; + + using Nss = modm::platform::GpioC1; + using Transport = modm::Sx128xTransportSpi; + +public: + TxThread() : timer(std::chrono::milliseconds(500)) {} + + inline bool + run() + { + PT_BEGIN(); + + Nss::setOutput(modm::Gpio::High); + Reset::setOutput(modm::Gpio::Low); + Busy::setInput(modm::platform::Gpio::InputType::PullDown); + + Dio1::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { tx::dio1 = true; }); + + Dio2::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { tx::dio2 = true; }); + + Dio3::setInput(modm::platform::Gpio::InputType::PullDown); + Exti::connect(Exti::Trigger::RisingEdge, [](uint8_t) { tx::dio3 = true; }); + + + PT_CALL(radio.reset()); + PT_CALL(radio.setStandby()); + + // Initialize the sx128x + PT_CALL(radio.setPacketType(PacketType::LoRa)); + PT_CALL(radio.setRfFrequency(2457_MHz / radio.frequencyLsb)); + PT_CALL(radio.setRegulatorMode(RegulatorMode::Ldo)); + PT_CALL(radio.setBufferBaseAddress(0, 0)); + PT_CALL(radio.setModulationParams(modulationParams)); + PT_CALL(radio.writeRegister(Register::SfAdditionalConfiguration, 0x32)); + PT_CALL(radio.writeRegister(Register::FrequencyErrorCorrection, 0x01)); + PT_CALL(radio.setPacketParams(packetParams)); + PT_CALL(radio.setDioIrqParams(Irq::TxDone | Irq::RxTxTimeout, Irq::TxDone, Irq::RxTxTimeout)); + + MODM_LOG_DEBUG << "Sx128x initialization complete!" << modm::endl; + + while (true) + { + if (tx::dio1.exchange(false)) + { + PT_CALL(radio.getIrqStatus(&irqStatus)); + if (irqStatus.any(Irq::TxDone)) + { + PT_CALL(radio.clearIrqStatus(Irq::TxDone)); + irqStatus.reset(Irq::TxDone); + + MODM_LOG_DEBUG << "Message sent" << modm::endl; + MODM_LOG_DEBUG << "Counter: " << counter << modm::endl; + counter++; + } + } + + if (tx::dio2.exchange(false)) + { + PT_CALL(radio.getIrqStatus(&irqStatus)); + if (irqStatus.any(Irq::RxTxTimeout)) + { + PT_CALL(radio.clearIrqStatus(Irq::RxTxTimeout)); + irqStatus.reset(Irq::RxTxTimeout); + MODM_LOG_DEBUG << "Received a timeout" << modm::endl; + } + } + + if (timer.execute()) + { + PT_CALL(radio.writeBuffer(0, std::span{(uint8_t *) &counter, sizeof(counter)})); + PT_CALL(radio.setTx(PeriodBase::ms1, 100)); + } + PT_YIELD(); + } + + PT_END(); + } + +private: + Irq_t irqStatus; + modm::Sx128x< Transport, Reset, Busy > radio; + + uint32_t counter = 0; + modm::PeriodicTimer timer; +}; + +int +main() +{ + Board::initialize(); + + SpiMaster::connect(); + SpiMaster::initialize(); + + MODM_LOG_INFO << "==========SX128x Test==========" << modm::endl; + MODM_LOG_DEBUG << "Debug logging here" << modm::endl; + MODM_LOG_INFO << "Info logging here" << modm::endl; + MODM_LOG_WARNING << "Warning logging here" << modm::endl; + MODM_LOG_ERROR << "Error logging here" << modm::endl; + MODM_LOG_INFO << "===============================" << modm::endl; + + while (true) + { + rxThread.run(); + } + + return 0; +} \ No newline at end of file diff --git a/examples/nucleo_g474re/sx128x_lora/project.xml b/examples/nucleo_g474re/sx128x_lora/project.xml new file mode 100644 index 0000000000..544f5d2171 --- /dev/null +++ b/examples/nucleo_g474re/sx128x_lora/project.xml @@ -0,0 +1,15 @@ + + modm:nucleo-g474re + + + + + modm:driver:sx128x + modm:platform:exti + modm:platform:gpio + modm:platform:spi:1 + modm:processing:protothread + modm:processing:timer + modm:build:scons + + \ No newline at end of file From 01c02d281c2c85443c76f115ba6c14817853dc72 Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Tue, 15 Aug 2023 19:21:29 +0200 Subject: [PATCH 048/159] [driver] Fix BdSpiFlash flipped waiting --- src/modm/driver/storage/block_device_spiflash_impl.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/modm/driver/storage/block_device_spiflash_impl.hpp b/src/modm/driver/storage/block_device_spiflash_impl.hpp index d55f205a7d..56ffb55fe1 100644 --- a/src/modm/driver/storage/block_device_spiflash_impl.hpp +++ b/src/modm/driver/storage/block_device_spiflash_impl.hpp @@ -77,10 +77,9 @@ modm::BdSpiFlash::read(uint8_t* buffer, bd_address_t address if((size == 0) || (size % BlockSizeRead != 0) || (address + size > flashSize)) { RF_RETURN(false); - } else { - RF_CALL(waitWhileBusy()); } + RF_CALL(waitWhileBusy()); RF_CALL(spiOperation(Instruction::FR, address, nullptr, buffer, size, 1)); RF_END_RETURN(true); @@ -95,12 +94,11 @@ modm::BdSpiFlash::program(const uint8_t* buffer, bd_address_ if((size == 0) || (size % BlockSizeWrite != 0) || (address + size > flashSize)) { RF_RETURN(false); - } else { - RF_CALL(waitWhileBusy()); } index = 0; while(index < size) { + RF_CALL(waitWhileBusy()); RF_CALL(spiOperation(Instruction::WE)); RF_CALL(spiOperation(Instruction::PP, address + index, &buffer[index], nullptr, BlockSizeWrite)); index += BlockSizeWrite; @@ -119,15 +117,15 @@ modm::BdSpiFlash::erase(bd_address_t address, bd_size_t size if((size == 0) || (size % BlockSizeErase != 0) || (address + size > flashSize)) { RF_RETURN(false); - } else { - RF_CALL(waitWhileBusy()); } if (address == 0 && size == flashSize) { + RF_CALL(waitWhileBusy()); RF_CALL(spiOperation(Instruction::CE)); } else { index = 0; while(index < size) { + RF_CALL(waitWhileBusy()); RF_CALL(spiOperation(Instruction::WE)); RF_CALL(spiOperation(Instruction::SE, address + index)); index += BlockSizeErase; From 74f9cad749659c7c46b92416c980e5bceb3380cf Mon Sep 17 00:00:00 2001 From: rasmuskleist Date: Fri, 4 Aug 2023 20:08:40 +0200 Subject: [PATCH 049/159] [example] Adding SpiFlash FatFs compatibility test The example is adapted from http://elm-chan.org/fsw/ff/res/app4.c --- .../spi_flash_fatfs/ffconf_local.h | 32 ++ .../nucleo_f429zi/spi_flash_fatfs/main.cpp | 340 ++++++++++++++++++ .../nucleo_f429zi/spi_flash_fatfs/project.xml | 17 + .../spi_flash_fatfs/spiflash_disk.cpp | 94 +++++ 4 files changed, 483 insertions(+) create mode 100644 examples/nucleo_f429zi/spi_flash_fatfs/ffconf_local.h create mode 100644 examples/nucleo_f429zi/spi_flash_fatfs/main.cpp create mode 100644 examples/nucleo_f429zi/spi_flash_fatfs/project.xml create mode 100644 examples/nucleo_f429zi/spi_flash_fatfs/spiflash_disk.cpp diff --git a/examples/nucleo_f429zi/spi_flash_fatfs/ffconf_local.h b/examples/nucleo_f429zi/spi_flash_fatfs/ffconf_local.h new file mode 100644 index 0000000000..2c6ff00805 --- /dev/null +++ b/examples/nucleo_f429zi/spi_flash_fatfs/ffconf_local.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 FFCONF_DEF +# error "Don't include this file directly, use 'ffconf.h' instead!" +#endif + +// Enable directory filtering: f_findfirst(), f_findnext() +#define FF_USE_FIND 1 + +// Enable Volume Formatting: f_mkfs() +#define FF_USE_MKFS 1 + +// Enable Volume Label: f_setlabel(), f_getlabel() +#define FF_USE_LABEL 1 + +// Configure minium sector size +#define FF_MIN_SS 4096 + +// Configure maximum sector size +#define FF_MAX_SS 4096 + +// Enable tiny sector buffers +#define FF_FS_TINY 1 diff --git a/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp b/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp new file mode 100644 index 0000000000..f6c1335a5d --- /dev/null +++ b/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 + +using namespace Board; +using SpiMaster = SpiMaster1; + +// Spi flash chip wiring: +using Cs = GpioA4; +using Mosi = GpioB5; +using Miso = GpioB4; +using Sck = GpioB3; +// Connect WP and HOLD pins to +3V3 +// and of course Vdd to +3V3 and Vss to GND + +/* The example is adapted from http://elm-chan.org/fsw/ff/res/app4.c */ + +DWORD buff[FF_MAX_SS]; /* Working buffer (4 sector in size) */ +BYTE pdrv = 0; /* Physical drive number to be checked (all data on the drive will be lost) */ +UINT ncyc = 3; /* Number of test cycles */ +UINT sz_buff = sizeof(buff); /* Size of the working buffer in unit of byte */ + +static DWORD pn ( /* Pseudo random number generator */ + DWORD pns /* 0:Initialize, !0:Read */ +) +{ + static DWORD lfsr; + UINT n; + + + if (pns) { + lfsr = pns; + for (n = 0; n < 32; n++) pn(0); + } + if (lfsr & 1) { + lfsr >>= 1; + lfsr ^= 0x80200003; + } else { + lfsr >>= 1; + } + return lfsr; +} + +int test_diskio() +{ + UINT n, cc, ns; + DWORD sz_drv, lba, lba2, sz_eblk, pns = 1; + WORD sz_sect; + BYTE *pbuff = (BYTE*)buff; + DSTATUS ds; + DRESULT dr; + + + MODM_LOG_INFO.printf("test_diskio(%u, %u, 0x%08X, 0x%08X)\n", pdrv, ncyc, (UINT)buff, sz_buff); + + if (sz_buff < FF_MAX_SS + 8) { + MODM_LOG_INFO.printf("Insufficient work area to run the program.\n"); + return 1; + } + + for (cc = 1; cc <= ncyc; cc++) { + MODM_LOG_INFO.printf("**** Test cycle %u of %u start ****\n", cc, ncyc); + + MODM_LOG_INFO.printf(" disk_initalize(%u)", pdrv); + ds = disk_initialize(pdrv); + if (ds & STA_NOINIT) { + MODM_LOG_INFO.printf(" - failed.\n"); + return 2; + } else { + MODM_LOG_INFO.printf(" - ok.\n"); + } + + MODM_LOG_INFO.printf("**** Get drive size ****\n"); + MODM_LOG_INFO.printf(" disk_ioctl(%u, GET_SECTOR_COUNT, 0x%08X)", pdrv, (UINT)&sz_drv); + sz_drv = 0; + dr = disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_drv); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 3; + } + if (sz_drv < 128) { + MODM_LOG_INFO.printf("Failed: Insufficient drive size to test.\n"); + return 4; + } + MODM_LOG_INFO.printf(" Number of sectors on the drive %u is %lu.\n", pdrv, sz_drv); + +#if FF_MAX_SS != FF_MIN_SS + MODM_LOG_INFO.printf("**** Get sector size ****\n"); + MODM_LOG_INFO.printf(" disk_ioctl(%u, GET_SECTOR_SIZE, 0x%X)", pdrv, (UINT)&sz_sect); + sz_sect = 0; + dr = disk_ioctl(pdrv, GET_SECTOR_SIZE, &sz_sect); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 5; + } + MODM_LOG_INFO.printf(" Size of sector is %u bytes.\n", sz_sect); +#else + sz_sect = FF_MAX_SS; +#endif + + MODM_LOG_INFO.printf("**** Get block size ****\n"); + MODM_LOG_INFO.printf(" disk_ioctl(%u, GET_BLOCK_SIZE, 0x%X)", pdrv, (UINT)&sz_eblk); + sz_eblk = 0; + dr = disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_eblk); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + } + if (dr == RES_OK || sz_eblk >= 2) { + MODM_LOG_INFO.printf(" Size of the erase block is %lu sectors.\n", sz_eblk); + } else { + MODM_LOG_INFO.printf(" Size of the erase block is unknown.\n"); + } + + /* Single sector write test */ + MODM_LOG_INFO.printf("**** Single sector write test ****\n"); + lba = 0; + for (n = 0, pn(pns); n < sz_sect; n++) pbuff[n] = (BYTE)pn(0); + MODM_LOG_INFO.printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba); + dr = disk_write(pdrv, pbuff, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 6; + } + MODM_LOG_INFO.printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv); + dr = disk_ioctl(pdrv, CTRL_SYNC, 0); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 7; + } + memset(pbuff, 0, sz_sect); + MODM_LOG_INFO.printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba); + dr = disk_read(pdrv, pbuff, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 8; + } + for (n = 0, pn(pns); n < sz_sect && pbuff[n] == (BYTE)pn(0); n++) ; + if (n == sz_sect) { + MODM_LOG_INFO.printf(" Read data matched.\n"); + } else { + MODM_LOG_INFO.printf(" Read data differs from the data written.\n"); + return 10; + } + pns++; + + MODM_LOG_INFO.printf("**** Multiple sector write test ****\n"); + lba = 5; ns = sz_buff / sz_sect; + if (ns > 4) ns = 4; + if (ns > 1) { + for (n = 0, pn(pns); n < (UINT)(sz_sect * ns); n++) pbuff[n] = (BYTE)pn(0); + MODM_LOG_INFO.printf(" disk_write(%u, 0x%X, %lu, %u)", pdrv, (UINT)pbuff, lba, ns); + dr = disk_write(pdrv, pbuff, lba, ns); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 11; + } + MODM_LOG_INFO.printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv); + dr = disk_ioctl(pdrv, CTRL_SYNC, 0); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 12; + } + memset(pbuff, 0, sz_sect * ns); + MODM_LOG_INFO.printf(" disk_read(%u, 0x%X, %lu, %u)", pdrv, (UINT)pbuff, lba, ns); + dr = disk_read(pdrv, pbuff, lba, ns); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 13; + } + for (n = 0, pn(pns); n < (UINT)(sz_sect * ns) && pbuff[n] == (BYTE)pn(0); n++) ; + if (n == (UINT)(sz_sect * ns)) { + MODM_LOG_INFO.printf(" Read data matched.\n"); + } else { + MODM_LOG_INFO.printf(" Read data differs from the data written.\n"); + return 14; + } + } else { + MODM_LOG_INFO.printf(" Test skipped.\n"); + } + pns++; + + MODM_LOG_INFO.printf("**** Single sector write test (unaligned buffer address) ****\n"); + lba = 5; + for (n = 0, pn(pns); n < sz_sect; n++) pbuff[n+3] = (BYTE)pn(0); + MODM_LOG_INFO.printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff+3), lba); + dr = disk_write(pdrv, pbuff+3, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 15; + } + MODM_LOG_INFO.printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv); + dr = disk_ioctl(pdrv, CTRL_SYNC, 0); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 16; + } + memset(pbuff+5, 0, sz_sect); + MODM_LOG_INFO.printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff+5), lba); + dr = disk_read(pdrv, pbuff+5, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 17; + } + for (n = 0, pn(pns); n < sz_sect && pbuff[n+5] == (BYTE)pn(0); n++) ; + if (n == sz_sect) { + MODM_LOG_INFO.printf(" Read data matched.\n"); + } else { + MODM_LOG_INFO.printf(" Read data differs from the data written.\n"); + return 18; + } + pns++; + + MODM_LOG_INFO.printf("**** 4GB barrier test ****\n"); + if (sz_drv >= 128 + 0x80000000 / (sz_sect / 2)) { + lba = 6; lba2 = lba + 0x80000000 / (sz_sect / 2); + for (n = 0, pn(pns); n < (UINT)(sz_sect * 2); n++) pbuff[n] = (BYTE)pn(0); + MODM_LOG_INFO.printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba); + dr = disk_write(pdrv, pbuff, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 19; + } + MODM_LOG_INFO.printf(" disk_write(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff+sz_sect), lba2); + dr = disk_write(pdrv, pbuff+sz_sect, lba2, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 20; + } + MODM_LOG_INFO.printf(" disk_ioctl(%u, CTRL_SYNC, NULL)", pdrv); + dr = disk_ioctl(pdrv, CTRL_SYNC, 0); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 21; + } + memset(pbuff, 0, sz_sect * 2); + MODM_LOG_INFO.printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)pbuff, lba); + dr = disk_read(pdrv, pbuff, lba, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 22; + } + MODM_LOG_INFO.printf(" disk_read(%u, 0x%X, %lu, 1)", pdrv, (UINT)(pbuff+sz_sect), lba2); + dr = disk_read(pdrv, pbuff+sz_sect, lba2, 1); + if (dr == RES_OK) { + MODM_LOG_INFO.printf(" - ok.\n"); + } else { + MODM_LOG_INFO.printf(" - failed.\n"); + return 23; + } + for (n = 0, pn(pns); pbuff[n] == (BYTE)pn(0) && n < (UINT)(sz_sect * 2); n++) ; + if (n == (UINT)(sz_sect * 2)) { + MODM_LOG_INFO.printf(" Read data matched.\n"); + } else { + MODM_LOG_INFO.printf(" Read data differs from the data written.\n"); + return 24; + } + } else { + MODM_LOG_INFO.printf(" Test skipped.\n"); + } + pns++; + + MODM_LOG_INFO.printf("**** Test cycle %u of %u completed ****\n\n", cc, ncyc); + } + + return 0; +} + +modm_faststack modm::Fiber<> testFiber(test_diskio); + +modm_faststack modm::Fiber<> blinkyFiber([]() +{ + Board::Leds::setOutput(); + while(true) + { + Board::Leds::toggle(); + modm::fiber::sleep(200ms); + } +}); + +int +main() +{ + // initialize board and SPI + Board::initialize(); + SpiMaster::connect(); + SpiMaster::initialize(); + + /* Check function/compatibility of the physical drive #0 */ + modm::fiber::Scheduler::run(); + + while (true); + + return 0; +} diff --git a/examples/nucleo_f429zi/spi_flash_fatfs/project.xml b/examples/nucleo_f429zi/spi_flash_fatfs/project.xml new file mode 100644 index 0000000000..ba04e1a17d --- /dev/null +++ b/examples/nucleo_f429zi/spi_flash_fatfs/project.xml @@ -0,0 +1,17 @@ + + modm:nucleo-f429zi + + + + + + + modm:build:scons + modm:driver:block.device:spi.flash + modm:fatfs + modm:platform:spi:1 + modm:platform:gpio + modm:processing:fiber + modm:processing:protothread + + diff --git a/examples/nucleo_f429zi/spi_flash_fatfs/spiflash_disk.cpp b/examples/nucleo_f429zi/spi_flash_fatfs/spiflash_disk.cpp new file mode 100644 index 0000000000..a537777968 --- /dev/null +++ b/examples/nucleo_f429zi/spi_flash_fatfs/spiflash_disk.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * 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 +#include +#include +#include + +using namespace Board; +using SpiMaster = SpiMaster1; + +// Spi flash chip wiring: +using Cs = GpioA4; +using Mosi = GpioB5; +using Miso = GpioB4; +using Sck = GpioB3; +// Connect WP and HOLD pins to +3V3 +// and of course Vdd to +3V3 and Vss to GND + +constexpr uint32_t SectorSize = 4096; +constexpr uint32_t SectorCount = 8192; +constexpr uint32_t FlashSize = SectorSize * SectorCount; + +modm::BdSpiFlash spiFlash; + +// ---------------------------------------------------------------------------- + +DSTATUS disk_initialize(BYTE pdrv) +{ + if (pdrv == 0) + { + if (spiFlash.initialize()) { + return 0; + } + } + return STA_NOINIT; +} + +DSTATUS disk_status(BYTE pdrv) +{ + return pdrv ? STA_NOINIT : 0; +} + +DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) +{ + if (pdrv == 0) + { + if (spiFlash.read(buff, sector * SectorSize, count * SectorSize)) { + return RES_OK; + } else { + return RES_ERROR; + } + } + return RES_NOTRDY; +} + +DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) +{ + if (pdrv == 0) + { + if (spiFlash.write(buff, sector * SectorSize, count * SectorSize)) { + return RES_OK; + } else { + return RES_ERROR; + } + } + return RES_NOTRDY; +} + +DRESULT disk_ioctl(BYTE pdrv, BYTE ctrl, void *buff) +{ + if (pdrv) return RES_NOTRDY; + switch (ctrl) + { + case CTRL_SYNC: return RES_OK; + case GET_SECTOR_COUNT: *(LBA_t*)buff = SectorCount; return RES_OK; + case GET_SECTOR_SIZE: *(WORD*) buff = SectorSize; return RES_OK; + case GET_BLOCK_SIZE: *(DWORD*)buff = 64; return RES_OK; + default: return RES_PARERR; + } +} From ffc02a37c006cd2809a1b0b06d1ae835619ae5e0 Mon Sep 17 00:00:00 2001 From: Alexander Solovets Date: Tue, 22 Aug 2023 23:52:19 -0700 Subject: [PATCH 050/159] Fix incorrect register indices in HMC58x3-based drivers --- src/modm/driver/inertial/hmc58x3.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modm/driver/inertial/hmc58x3.hpp b/src/modm/driver/inertial/hmc58x3.hpp index 2fc335b471..d17820c73b 100644 --- a/src/modm/driver/inertial/hmc58x3.hpp +++ b/src/modm/driver/inertial/hmc58x3.hpp @@ -47,9 +47,9 @@ struct hmc58x3 DataZ_Lsb = 0x07, DataZ_Msb = 0x08, Status = 0x09, - IdA = 0x10, - IdB = 0x11, - IdC = 0x12, + IdA = 0x0a, + IdB = 0x0b, + IdC = 0x0c, }; /// @endcond From d37fa244b5a3c5db34889130294e2fd19ecbf990 Mon Sep 17 00:00:00 2001 From: Alexander Solovets Date: Mon, 28 Aug 2023 14:28:47 -0700 Subject: [PATCH 051/159] Fix typo in a sample AVR code --- src/modm/platform/gpio/at90_tiny_mega/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/gpio/at90_tiny_mega/module.md b/src/modm/platform/gpio/at90_tiny_mega/module.md index 8259a01b8f..c509b55962 100644 --- a/src/modm/platform/gpio/at90_tiny_mega/module.md +++ b/src/modm/platform/gpio/at90_tiny_mega/module.md @@ -144,7 +144,7 @@ GpioB2::enablePcInterrupt(); MODM_ISR(PCINT0) { if (GpioB1::getPcInterruptFlag()) { - bool state = GpioB2::read(); + bool state = GpioB1::read(); // your code GpioB1::acknowledgePcInterruptFlag(); } From 5e4d09ea08c1d1a061ff964f2e44cb2c97dadcf9 Mon Sep 17 00:00:00 2001 From: Alexander Solovets Date: Mon, 28 Aug 2023 22:26:10 -0700 Subject: [PATCH 052/159] Fix incorrect register names The fix does not really affect the driver behaviour, but the incorrect register names make a confusion regarding the need to convert from big-endian byte order as as-is the code implies little-endian order. --- src/modm/driver/inertial/hmc58x3.hpp | 12 ++++++------ src/modm/driver/inertial/hmc58x3_impl.hpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modm/driver/inertial/hmc58x3.hpp b/src/modm/driver/inertial/hmc58x3.hpp index d17820c73b..39e65a5535 100644 --- a/src/modm/driver/inertial/hmc58x3.hpp +++ b/src/modm/driver/inertial/hmc58x3.hpp @@ -40,12 +40,12 @@ struct hmc58x3 ConfigA = 0x00, ConfigB = 0x01, Mode = 0x02, - DataX_Lsb = 0x03, - DataX_Msb = 0x04, - DataY_Lsb = 0x05, - DataY_Msb = 0x06, - DataZ_Lsb = 0x07, - DataZ_Msb = 0x08, + DataX_Msb = 0x03, + DataX_Lsb = 0x04, + DataY_Msb = 0x05, + DataY_Lsb = 0x06, + DataZ_Msb = 0x07, + DataZ_Lsb = 0x08, Status = 0x09, IdA = 0x0a, IdB = 0x0b, diff --git a/src/modm/driver/inertial/hmc58x3_impl.hpp b/src/modm/driver/inertial/hmc58x3_impl.hpp index 5f63beedc0..96047eecd3 100644 --- a/src/modm/driver/inertial/hmc58x3_impl.hpp +++ b/src/modm/driver/inertial/hmc58x3_impl.hpp @@ -30,7 +30,7 @@ modm::Hmc58x3::readMagneticField() { RF_BEGIN(); - if (RF_CALL(read(Register::DataX_Lsb, rawBuffer+3, 7))) + if (RF_CALL(read(Register::DataX_Msb, rawBuffer+3, 7))) { std::memcpy(data.data, rawBuffer+3, 6); RF_RETURN(true); From 3395235c77476337cdb429e58e326dbf9084470b Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 20 Jul 2023 13:59:47 +0200 Subject: [PATCH 053/159] [stm32] Fix spurious EXTI interrupt with shared IRQ In case of multiple event lines with a shared IRQ all enabled handlers were always called even when the respective event was not triggered. --- src/modm/platform/extint/stm32/exti.cpp.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modm/platform/extint/stm32/exti.cpp.in b/src/modm/platform/extint/stm32/exti.cpp.in index 3ab04e0f45..e4ca79701e 100644 --- a/src/modm/platform/extint/stm32/exti.cpp.in +++ b/src/modm/platform/extint/stm32/exti.cpp.in @@ -20,6 +20,11 @@ Exti::handlers[Exti::Lines] modm_fastdata; void Exti::irq(uint32_t mask) { uint32_t flags = EXTI->IMR{{"1" if (extended or separate_flags) else ""}} & mask; +%% if separate_flags + flags &= (EXTI->FPR1 & EXTI->FTSR1) | (EXTI->RPR1 & EXTI->RTSR1); +%% else + flags &= EXTI->PR{{"1" if extended else ""}}; +%% endif while(flags) { const uint8_t lmb = 31ul - __builtin_clz(flags); From c9e5227ef56396dd4c99663cf8deaff19f4a9fb6 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 20:26:29 +0200 Subject: [PATCH 054/159] [stm32] Fix compilation error when Exti::disconnect() is used --- src/modm/platform/extint/stm32/exti.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/extint/stm32/exti.hpp.in b/src/modm/platform/extint/stm32/exti.hpp.in index 72daa6b1b3..26f9023482 100644 --- a/src/modm/platform/extint/stm32/exti.hpp.in +++ b/src/modm/platform/extint/stm32/exti.hpp.in @@ -268,7 +268,7 @@ public: NVIC_DisableIRQ(IRQn_Type(vector)); } template< class Pin > static void - disableVector(uint8_t priority) { disableVector(getVectorForLine(Pin::pin)); } + disableVector() { disableVector(getVectorForLine(Pin::pin)); } inline static void disableVectors(MaskType mask) From c4222a7540bb3957616c70df023ed8c41d190415 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 2 Sep 2023 16:53:53 +0200 Subject: [PATCH 055/159] [ci] Split STM32G0 compile all job into two --- .github/workflows/compile-all.yml | 34 +++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile-all.yml b/.github/workflows/compile-all.yml index cc75ebe49e..3f2dc9c2e0 100644 --- a/.github/workflows/compile-all.yml +++ b/.github/workflows/compile-all.yml @@ -552,7 +552,7 @@ jobs: name: stm32u5-compile-all path: test/all/log - stm32g0-compile-all: + stm32g0-compile-all-1: if: github.event.label.name == 'ci:hal' runs-on: ubuntu-22.04 container: @@ -568,14 +568,40 @@ jobs: - name: Update lbuild run: | pip3 install --upgrade --upgrade-strategy=eager modm - - name: Compile HAL for all STM32G0 + - name: Compile HAL for all STM32G0 Part 1 run: | - (cd test/all && python3 run_all.py stm32g0 --quick-remaining) + (cd test/all && python3 run_all.py stm32g0 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() uses: actions/upload-artifact@v3 with: - name: stm32g0-compile-all + name: stm32g0-compile-all-1 + path: test/all/log + + stm32g0-compile-all-2: + if: github.event.label.name == 'ci:hal' + runs-on: ubuntu-22.04 + container: + image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + - name: Fix Git permission/ownership problem + run: | + git config --global --add safe.directory /__w/modm/modm + - name: Update lbuild + run: | + pip3 install --upgrade --upgrade-strategy=eager modm + - name: Compile HAL for all STM32G0 Part 2 + run: | + (cd test/all && python3 run_all.py stm32g0 --quick-remaining --split 2 --part 1) + - name: Upload log artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: stm32g0-compile-all-2 path: test/all/log stm32g4-compile-all-1: From 32adefe5e1a143334428d0eeb3bc19240abac47c Mon Sep 17 00:00:00 2001 From: Alexander Solovets Date: Mon, 4 Sep 2023 04:39:00 -0700 Subject: [PATCH 056/159] Improve file names processing in clang-format.sh - Process .cc and .hh files. - Ignore empty file list. --- tools/scripts/clang-format.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/scripts/clang-format.sh b/tools/scripts/clang-format.sh index f51966caa2..0b96d0d54c 100755 --- a/tools/scripts/clang-format.sh +++ b/tools/scripts/clang-format.sh @@ -8,4 +8,4 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. #---------------------------------------------------------------------------- -git diff --name-only --diff-filter=A -C -M develop | grep -e "\.\(c\|h\|hpp\|cpp\)\$" | xargs clang-format -i "$@" +git diff --name-only --diff-filter=A -C -M develop | grep -e "\.\(c\|h\|cc\|hh\|hpp\|cpp\)\$" | xargs -r clang-format -i "$@" From 93af956745849d5bb2626c55f68d0fba20b69405 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 10 Sep 2023 19:43:57 +0200 Subject: [PATCH 057/159] [board] Default putchar_ to use the board logger --- examples/stm32f469_discovery/printf/main.cpp | 10 ---------- src/modm/board/board.cpp.in | 11 +++++++++-- src/modm/board/module.md | 3 ++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/stm32f469_discovery/printf/main.cpp b/examples/stm32f469_discovery/printf/main.cpp index d6152fe0f6..a9eb8d7f1e 100644 --- a/examples/stm32f469_discovery/printf/main.cpp +++ b/examples/stm32f469_discovery/printf/main.cpp @@ -11,16 +11,6 @@ #include -extern "C" -void putchar_(char c) -{ - // Redirect the printf output to UART. - Board::stlink::Uart::write(c); - // or IOStream if the board already has a logger - // MODM_LOG_INFO << c; -} - -// ---------------------------------------------------------------------------- int main() { diff --git a/src/modm/board/board.cpp.in b/src/modm/board/board.cpp.in index 6a4e0e79cd..890eb10ca8 100644 --- a/src/modm/board/board.cpp.in +++ b/src/modm/board/board.cpp.in @@ -14,7 +14,7 @@ %% if with_assert #include %% endif - +%# %% if with_logger Board::LoggerDevice loggerDevice; @@ -23,8 +23,15 @@ 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); -%% endif +// Default all calls to printf to the UART +modm_extern_c void +putchar_(char c) +{ + loggerDevice.write(c); +} +%% endif +%# %% if with_assert modm_extern_c void modm_abandon(const modm::AssertionInfo &info) diff --git a/src/modm/board/module.md b/src/modm/board/module.md index c7960b06de..92ace1c563 100644 --- a/src/modm/board/module.md +++ b/src/modm/board/module.md @@ -25,7 +25,8 @@ The BSPs all use a common interface within a top-level `namespace Board`: `modm::platform::Gpio{port}{pin}`. If the board supports a dedicated serial logging output the BSP redirects the -`modm:debug` module debug stream `MODM_LOG_INFO` etc. +`modm:debug` module debug stream `MODM_LOG_INFO` as well as the output of the +standalone `printf` function. Please note that YOU must explicitly call the `Board` functions to initialize your hardware, just including the board module is not enough. From 7f3096149cf94e3e410bddaeb2e5de7107b681e9 Mon Sep 17 00:00:00 2001 From: Alexander Solovets Date: Mon, 11 Sep 2023 00:31:11 +0200 Subject: [PATCH 058/159] [driver] Implement QMC5883L driver --- README.md | 9 +- examples/avr/qmc5883l/main.cpp | 62 ++++++++ examples/avr/qmc5883l/project.xml | 11 ++ src/modm/driver/inertial/qmc5883l.hpp | 196 ++++++++++++++++++++++++++ src/modm/driver/inertial/qmc5883l.lb | 41 ++++++ 5 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 examples/avr/qmc5883l/main.cpp create mode 100644 examples/avr/qmc5883l/project.xml create mode 100644 src/modm/driver/inertial/qmc5883l.hpp create mode 100644 src/modm/driver/inertial/qmc5883l.lb diff --git a/README.md b/README.md index fc4f82adb6..83fba333a1 100644 --- a/README.md +++ b/README.md @@ -789,30 +789,31 @@ your specific needs. PCA9535 PCA9548A PCA9685 +QMC5883L SH1106 -SIEMENS-S65 +SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 SSD1306 ST7586S -ST7789 +ST7789 STTS22H STUSB4500 SX1276 SX128X TCS3414 -TCS3472 +TCS3472 TLC594x TMP102 TMP12x TMP175 TOUCH2046 -VL53L0 +VL53L0 VL6180 WS2812 diff --git a/examples/avr/qmc5883l/main.cpp b/examples/avr/qmc5883l/main.cpp new file mode 100644 index 0000000000..ebfe0bb8bb --- /dev/null +++ b/examples/avr/qmc5883l/main.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, Alexander Solovets + * + * 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; + +using Compass = Qmc5883l; +Compass::Data data; +Compass compass(data); +modm::atomic::Flag dataReady(true); + +MODM_ISR(INT2) +{ + dataReady.set(); +} + +int +main() +{ + Board::initialize(); + // DRDY pin must be connected to Board pin 19 (Port D2). + Board::D19::setInput(Gpio::InputType::PullUp); + Board::D19::setInputTrigger(Gpio::InputTrigger::RisingEdge); + Board::D19::enableExternalInterrupt(); + + RF_CALL_BLOCKING(compass.initialize()); + auto mode = Compass::Mode_t(Compass::Mode::Continious); + auto rate = Compass::OutputDataRate_t(Compass::OutputDataRate::_10Hz); + auto scale = Compass::FullScale_t(Compass::FullScale::_8G); + RF_CALL_BLOCKING(compass.configure(mode, rate | scale)); + + for (;;) + { + if (dataReady.testAndSet(false)) + { + if (RF_CALL_BLOCKING(compass.readData())) + { + MODM_LOG_INFO << "X:" << compass.x() << " Y: " << compass.y() + << " Z: " << compass.z() + << " OVL: " << (compass.status() & Compass::Status::OVL) + << modm::endl; + } else + { + serialStream << "readDataBlocking(): Error: " << uint8_t(I2cMaster::getErrorState()) + << modm::endl; + } + } + } +} diff --git a/examples/avr/qmc5883l/project.xml b/examples/avr/qmc5883l/project.xml new file mode 100644 index 0000000000..dd13835c4f --- /dev/null +++ b/examples/avr/qmc5883l/project.xml @@ -0,0 +1,11 @@ + + modm:mega-2560-pro + + + + + modm:platform:i2c + modm:driver:qmc5883l + modm:build:scons + + diff --git a/src/modm/driver/inertial/qmc5883l.hpp b/src/modm/driver/inertial/qmc5883l.hpp new file mode 100644 index 0000000000..18d4ececcd --- /dev/null +++ b/src/modm/driver/inertial/qmc5883l.hpp @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include +#include + +template +class Qmc5883l; + +struct Qmc5883lRegisters +{ +protected: + /// @cond + /// The addresses of the Configuration and Data Registers + enum class Register : uint8_t + { + DataX_Lsb = 0x00, + DataX_Msb = 0x01, + DataY_Lsb = 0x02, + DataY_Msb = 0x03, + DataZ_Lsb = 0x04, + DataZ_Msb = 0x05, + Status = 0x06, + Tout_Lsb = 0x07, + Tout_Msb = 0x08, + Control1 = 0x09, + Control2 = 0x0a, + SetReset = 0x0b, + }; + /// @endcond +public: + enum class Status : uint8_t + { + DOR = modm::Bit2, + OVL = modm::Bit1, + DRDY = modm::Bit0, + }; + MODM_FLAGS8(Status); + +public: + enum class Control1 : uint8_t + { + OSR1 = modm::Bit7, + OSR0 = modm::Bit6, + OversampleRate_Mask = OSR1 | OSR0, + + RNG1 = modm::Bit5, + RNG0 = modm::Bit4, + FullScale_Mask = RNG1 | RNG0, + + ODR1 = modm::Bit3, + ODR0 = modm::Bit2, + OutputDataRate_Mask = ODR1 | ODR0, + + MODE1 = modm::Bit1, + MODE0 = modm::Bit0, + Mode_Mask = MODE1 | MODE0, + }; + MODM_FLAGS8(Control1); + + enum class Control2 : uint8_t + { + SOFT_RST = modm::Bit7, + ROL_PNT = modm::Bit6, + INT_ENB = modm::Bit0, + }; + MODM_FLAGS8(Control2); + +public: + enum class OversampleRate : uint8_t + { + _512 = 0, + _256 = int(Control1::OSR0), + _128 = int(Control1::OSR1), + _64 = int(Control1::OSR0) | int(Control1::OSR1), + }; + + MODM_FLAGS_CONFIG(Control1, OversampleRate); + + enum class FullScale : uint8_t + { + _2G = 0, + _8G = int(Control1::RNG0), + }; + + MODM_FLAGS_CONFIG(Control1, FullScale); + + enum class OutputDataRate : uint8_t + { + _10Hz = 0, + _50Hz = int(Control1::ODR0), + _100Hz = int(Control1::ODR1), + _200Hz = int(Control1::ODR0) | int(Control1::ODR1), + }; + + MODM_FLAGS_CONFIG(Control1, OutputDataRate); + + enum class Mode : uint8_t + { + StandBy = 0, + Continious = int(Control1::MODE0), + }; + + MODM_FLAGS_CONFIG(Control1, Mode); + +public: + struct modm_packed Data + { + template + friend class Qmc5883l; + + protected: + uint8_t data[7]; + + template + int16_t inline getWord() + { + static_assert(Offset < sizeof data); + const auto value = reinterpret_cast(data + Offset); + return modm::fromLittleEndian(*value); + } + }; +}; + +template +class Qmc5883l : public Qmc5883lRegisters, public modm::I2cDevice +{ + /// @cond + Data &data; + /// @endcond + uint8_t buffer[sizeof data.data]; + + modm::ResumableResult + writeRegister(Register reg, uint8_t value) + { + RF_BEGIN(); + + buffer[0] = uint8_t(reg); + buffer[1] = value; + this->transaction.configureWrite(buffer, 2); + + RF_END_RETURN_CALL(this->runTransaction()); + } + +public: + Qmc5883l(Data &data, uint8_t address = 0x0d) : modm::I2cDevice(address), data(data) + {} + + auto x() { return data.getWord(); } + + auto y() { return data.getWord(); } + + auto z() { return data.getWord(); } + + auto status() { return Status_t(data.data[uint8_t(Register::Status)]); } + +public: + modm::ResumableResult + initialize() + { + // Per datasheet: + // wihtout any additional explanations recommended to set to 0x01. + return writeRegister(Register::SetReset, 0x01); + } + + modm::ResumableResult + configure(Mode_t mode, Control1_t control) + { + control |= mode; + return writeRegister(Register::Control1, control.value); + } + + modm::ResumableResult + configure(Control2_t control) + { + return writeRegister(Register::Control2, control.value); + } + + modm::ResumableResult + readData() + { + RF_BEGIN(); + + buffer[0] = uint8_t(Register::DataX_Lsb); + this->transaction.configureWriteRead(buffer, 1, buffer, sizeof buffer); + + if (RF_CALL(this->runTransaction())) + { + std::copy_n(buffer, sizeof data.data, data.data); + RF_RETURN(true); + } + + RF_END_RETURN(false); + } +}; diff --git a/src/modm/driver/inertial/qmc5883l.lb b/src/modm/driver/inertial/qmc5883l.lb new file mode 100644 index 0000000000..fae3c4466d --- /dev/null +++ b/src/modm/driver/inertial/qmc5883l.lb @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Alexander Solovets +# +# 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 = ":driver:qmc5883l" + module.description = """\ +# QMC5883L 3-Axis Digital Magnetometer + +The QMC5883L is a multi-chip three-axis magnetic sensor. This surface-mount, +small sized chip has integrated magnetic sensors with signal condition ASIC, +targeted for high precision applications such as compassing, navigation and +gaming in drone, robot, mobile and personal hand-held devices. + +The QMC5883L is based on state-of-the-art, high resolution, magneto-resistive +technology licensed from Honeywell AMR technology. Along with custom-designed +16-bit ADC ASIC, it offers the advantages of low noise, high accuracy, low +power consumption, offset cancellation and temperature compensation. QMC5883L +enables 1° to 2° compass heading accuracy. The I²C serial bus allows for easy +interface. +""" + +def prepare(module, options): + module.depends( + ":architecture:i2c.device", + ":architecture:register", + ":math:utils") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/inertial" + env.copy("qmc5883l.hpp") From 17d8a7e61e987926cfeabe8a53fe4243b682bc84 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Mon, 18 Sep 2023 15:24:56 +0200 Subject: [PATCH 059/159] LM75 temp sensor: fix utils/endianness dependency --- src/modm/driver/temperature/lm75.hpp | 1 + src/modm/driver/temperature/lm75.lb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modm/driver/temperature/lm75.hpp b/src/modm/driver/temperature/lm75.hpp index d9cb722710..0bb74bb64d 100644 --- a/src/modm/driver/temperature/lm75.hpp +++ b/src/modm/driver/temperature/lm75.hpp @@ -16,6 +16,7 @@ #include #include +#include namespace modm { diff --git a/src/modm/driver/temperature/lm75.lb b/src/modm/driver/temperature/lm75.lb index 560364648f..fe757b9583 100644 --- a/src/modm/driver/temperature/lm75.lb +++ b/src/modm/driver/temperature/lm75.lb @@ -29,7 +29,8 @@ hardcoded into the sensor and cannot be changed. def prepare(module, options): module.depends( ":architecture:i2c.device", - ":architecture:register") + ":architecture:register", + ":math:utils") return True def build(env): From 9b961f6898d7702e105b29177c192a41b747a7dc Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 20 Sep 2023 23:44:15 +0200 Subject: [PATCH 060/159] [docs] Update macOS installation of toolchain --- docs/src/guide/installation.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/src/guide/installation.md b/docs/src/guide/installation.md index f04e18c26f..d6f937ce80 100644 --- a/docs/src/guide/installation.md +++ b/docs/src/guide/installation.md @@ -152,6 +152,15 @@ brew install python3 scons git doxygen pip3 install modm ``` +!!! warning "Missing pyelftools" + If you get errors about missing `pyelftools` when calling `scons`, you may + be using the system Python, rather than the Homebrew Python. + In that case, you can add this line to your `.bashrc` or `.zshrc`: + + ```sh + alias scons="/usr/bin/env python3 $(which scons)" + ``` + We recommend using a graphical frontend for GDB called [gdbgui][]: ```sh @@ -172,8 +181,8 @@ Install the [pre-built ARM toolchain](https://github.com/osx-cross/homebrew-arm) ```sh brew tap osx-cross/arm -brew install arm-gcc-bin@12 -brew install openocd --HEAD +brew install arm-gcc-bin@12 openocd +brew link --force arm-gcc-bin@12 ``` To program Microchip SAM devices via the bootloader, install the `bossac` tool: From d22ef294a6bfd2011334fc8b8da32ce0fd6beb6d Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 1 Oct 2023 21:29:54 +0200 Subject: [PATCH 061/159] [ext] Remove peripheral typedef in device header They do not work reliably. --- ext/st/device.hpp.in | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ext/st/device.hpp.in b/ext/st/device.hpp.in index a92f151e54..0862829dbd 100644 --- a/ext/st/device.hpp.in +++ b/ext/st/device.hpp.in @@ -61,19 +61,4 @@ #define RCC_CFGR_PPRE_DIV16 (0x00007000UL) /*!< HCLK divided by 16 */ %% endif -/// @cond -// This is a hack to make the *_Typedef's known to GDB, so that you can debug -// the peripherals directly in GDB in any context. -// Otherwise GDB would throw a "no symbol 'GPIO_TypeDef' in current context". -%% for (name, type) in peripherals -%% if type == "SAU_Type" -#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) -%% endif -extern {{ type | lbuild.pad(19) }} ___{{ name | lbuild.pad(15) }}; -%% if type == "SAU_Type" -#endif /* defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) */ -%% endif -%% endfor -/// @endcond - #endif // MODM_DEVICE_HPP From 36f7bfd2614db0b3632667174b1d9c16e5de6881 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 1 Oct 2023 18:15:58 +0200 Subject: [PATCH 062/159] [ext] Update submodules --- README.md | 4 ++-- ext/arm/cmsis-dsp | 2 +- ext/etlcpp/etl | 2 +- ext/gcc/libstdc++ | 2 +- ext/lvgl/lvgl | 2 +- ext/modm-devices | 2 +- ext/st/stm32 | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 83fba333a1..80bf649f33 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ git clone --recurse-submodules --jobs 8 https://github.com/modm-io/modm.git ## Microcontrollers -modm can create a HAL for 3715 devices of these vendors: +modm can create a HAL for 3734 devices of these vendors: -- STMicroelectronics STM32: 2910 devices. +- STMicroelectronics STM32: 2929 devices. - Microchip SAM: 416 devices. - Microchip AVR: 388 devices. - Raspberry Pi: 1 device. diff --git a/ext/arm/cmsis-dsp b/ext/arm/cmsis-dsp index dd6fc082c7..db8d7a5b9c 160000 --- a/ext/arm/cmsis-dsp +++ b/ext/arm/cmsis-dsp @@ -1 +1 @@ -Subproject commit dd6fc082c7f35d253b04a154d90a59a3564af432 +Subproject commit db8d7a5b9c9a65906e3cd5bb8f91a933e4ff75f6 diff --git a/ext/etlcpp/etl b/ext/etlcpp/etl index 088343986b..894ce959fe 160000 --- a/ext/etlcpp/etl +++ b/ext/etlcpp/etl @@ -1 +1 @@ -Subproject commit 088343986b8c0134cefe1a24d25312c49fce1f57 +Subproject commit 894ce959fe8fd9a1ccb3b04a060901919df8886b diff --git a/ext/gcc/libstdc++ b/ext/gcc/libstdc++ index dd2953c4c5..9d10e00470 160000 --- a/ext/gcc/libstdc++ +++ b/ext/gcc/libstdc++ @@ -1 +1 @@ -Subproject commit dd2953c4c565262ce2be2147bb1cbb8188ffa2df +Subproject commit 9d10e0047011f4c1e6ef4dbf76aca5e9a340a6f8 diff --git a/ext/lvgl/lvgl b/ext/lvgl/lvgl index b51785ea99..88f2e40bcf 160000 --- a/ext/lvgl/lvgl +++ b/ext/lvgl/lvgl @@ -1 +1 @@ -Subproject commit b51785ea997ac8e1d0f0b3b4e1b247349cee7217 +Subproject commit 88f2e40bcf9494dee2d71b11a377c039ef83c372 diff --git a/ext/modm-devices b/ext/modm-devices index e540dc9d44..9186079eed 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit e540dc9d4430749fe0cbc20b536d71136b6f25e4 +Subproject commit 9186079eedde2108d8cf0387e83d73a1b5647179 diff --git a/ext/st/stm32 b/ext/st/stm32 index c48d28b5c0..0f82e9d808 160000 --- a/ext/st/stm32 +++ b/ext/st/stm32 @@ -1 +1 @@ -Subproject commit c48d28b5c0049958e47add3b739826965c56262f +Subproject commit 0f82e9d808e36d45dd39c49db47d963826c2a354 From f90360df1c190dda2485ff73712c0c7c28349147 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 1 Oct 2023 21:29:03 +0200 Subject: [PATCH 063/159] [release] Update changelog for 2023q3 release --- .mailmap | 3 + CHANGELOG.md | 134 +++++++++++++++++++++++++++++++++++++++ docs/release/2023q3.md | 106 +++++++++++++++++++++++++++++++ tools/scripts/authors.py | 2 + 4 files changed, 245 insertions(+) create mode 100644 docs/release/2023q3.md diff --git a/.mailmap b/.mailmap index 1608d7d4b9..432145a100 100644 --- a/.mailmap +++ b/.mailmap @@ -1,3 +1,5 @@ +Alexander Solovets +Alexander Solovets Amar Amarok McLion Andre Gilerson @@ -38,6 +40,7 @@ Jörg Hoffmann Kaelin Laundry Kevin Läufer Kevin Läufer +Klaus Schnass Linas Nikiperavicius Lucas Mösch Lucas Mösch diff --git a/CHANGELOG.md b/CHANGELOG.md index bc703a9331..4afea26ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,114 @@ pay attention to. Medium impact changes are also worth looking at. +## 2023-10-01: 2023q3 release + +This release covers everything from 2023-07-01 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Features: + +- STM32G0 FD-CAN driver. +- Redirect printf to logger in BSPs that support it. + +Integrated Projects: + +- CMSIS-DSP upgraded to v1.15.0. +- ETL upgraded to v20.38.2. +- LVGL upgraded to v8.3.10. +- STM32C0 headers upgraded to v1.1.0. +- STM32H5 headers upgraded to v1.1.0. +- STM32L4 headers upgraded to v1.7.3. +- STM32U5 headers upgraded to v1.3.0. + +Fixes: + +- Place `.data` section in DMA-able memory on STM32H7. +- Correct identification register indices for HMC58x3 drivers. +- Fix spurious EXTI interrupt with shared IRQ. + +New device drivers: + +- IIM-42652 IMU family driver as [`modm:driver:ixm42xxx`][]. +- Stacked SPI Flash driver as [`modm:driver:block.device:spi.stack.flash`][]. +- SX128x LoRa driver as [`modm:driver:sx128x`][]. +- QMC5883L magnetometer driver as [`modm:driver:qmc5883l`][]. + +Known bugs: + +- STM32F7: D-Cache not enabled by default. See [#485][]. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See [#285][]. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See [#310][]. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See [#591][]. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Alexander Solovets ([@mbait][]) 🎉 +- Christopher Durand ([@chris-durand][]) +- Klaus Schnass ([@klsc-zeat][]) 🎉 +- Niklas Hauser ([@salkinium][]) +- Raphael Lehmann ([@rleh][]) +- Rasmus Kleist ([@rasmuskleist][]) + +PR [#1088][] -> [2023q3][]. + +
+Detailed changelog + +#### 2023-09-11: Add QMC5883L magnetometer driver + +PR [#1063][] -> [7f30961][]. +Tested in hardware by [@mbait][]. + +#### 2023-09-02: Fix EXTI interrupts + +PR [#1066][] -> [c9e5227][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-08-15: Add SX128x LoRa driver + +PR [#1050][] -> [517bd84][]. +Tested in hardware by [@rasmuskleist][]. + +#### 2023-08-13: Add Stacked SPI Flash driver + +PR [#1054][] -> [ba23833][]. +Tested in hardware by [@rasmuskleist][]. + +#### 2023-07-27: Add ADC driver for STM32H7 + +Also adds support for injected conversions. + +PR [#1049][] -> [4d69227][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-07-26: Fix ADC driver for STM32G0 + +PR [#1053][] -> [5d03d53][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-07-21: Add CAN driver for STM32G0 + +PR [#1051][] -> [a05cc62][]. +Tested in hardware by [@klsc-zeat][]. + +#### 2023-07-19: Add IIM-42652 IMU driver + +PR [#1040][] -> [8012d82][]. +Tested in hardware by [@rasmuskleist][]. + +#### 2023-07-10: Place `.data` section into D1_SRAM on STM32H7 + +PR [#1048][] -> [027811f][]. +Tested in hardware by [@chris-durand][]. + +
+ + ## 2023-07-01: 2023q2 release This release covers everything from 2023-04-05 and has been tested with avr-gcc @@ -2660,6 +2768,7 @@ Please note that contributions from xpcc were continuously ported to modm. [2022q4]: https://github.com/modm-io/modm/releases/tag/2022q4 [2023q1]: https://github.com/modm-io/modm/releases/tag/2023q1 [2023q2]: https://github.com/modm-io/modm/releases/tag/2023q2 +[2023q3]: https://github.com/modm-io/modm/releases/tag/2023q3 [@19joho66]: https://github.com/19joho66 [@ASMfreaK]: https://github.com/ASMfreaK @@ -2690,12 +2799,14 @@ Please note that contributions from xpcc were continuously ported to modm. [@henrikssn]: https://github.com/henrikssn [@hshose]: https://github.com/hshose [@jasa]: https://github.com/jasa +[@klsc-zeat]: https://github.com/klsc-zeat [@lgili]: https://github.com/lgili [@linasnikis]: https://github.com/linasnikis [@lmoesch]: https://github.com/lmoesch [@lukh]: https://github.com/lukh [@luxarf]: https://github.com/luxarf [@mat-kie]: https://github.com/mat-kie +[@mbait]: https://github.com/mbait [@mcbridejc]: https://github.com/mcbridejc [@mikewolfram]: https://github.com/mikewolfram [@nesos]: https://github.com/nesos @@ -2757,6 +2868,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:ads816x`]: https://modm.io/reference/module/modm-driver-ads816x [`modm:driver:apa102`]: https://modm.io/reference/module/modm-driver-apa102 [`modm:driver:at24mac402`]: https://modm.io/reference/module/modm-driver-at24mac402 +[`modm:driver:block.device:spi.stack.flash`]: https://modm.io/reference/module/modm-driver-block-device-spi-stack-flash [`modm:driver:bno055`]: https://modm.io/reference/module/modm-driver-bno055 [`modm:driver:cat24aa`]: https://modm.io/reference/module/modm-driver-cat24aa [`modm:driver:cycle_counter`]: https://modm.io/reference/module/modm-driver-cycle_counter @@ -2766,6 +2878,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:gpio-sampler`]: https://modm.io/reference/module/modm-driver-gpio-sampler [`modm:driver:ili9341`]: https://modm.io/reference/module/modm-driver-ili9341 [`modm:driver:is31fl3733`]: https://modm.io/reference/module/modm-driver-is31fl3733 +[`modm:driver:ixm42xxx`]: https://modm.io/reference/module/modm-driver-ixm42xxx [`modm:driver:lan8720a`]: https://modm.io/reference/module/modm-driver-lan8720a [`modm:driver:lis3mdl`]: https://modm.io/reference/module/modm-driver-lis3mdl [`modm:driver:lp503x`]: https://modm.io/reference/module/modm-driver-lp503x @@ -2780,6 +2893,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:ms5837`]: https://modm.io/reference/module/modm-driver-ms5837 [`modm:driver:pat9125el`]: https://modm.io/reference/module/modm-driver-pat9125el [`modm:driver:pca9548a`]: https://modm.io/reference/module/modm-driver-pca9548a +[`modm:driver:qmc5883l`]: https://modm.io/reference/module/modm-driver-qmc5883l [`modm:driver:sh1106`]: https://modm.io/reference/module/modm-driver-sh1106 [`modm:driver:sk6812`]: https://modm.io/reference/module/modm-driver-sk6812 [`modm:driver:sk9822`]: https://modm.io/reference/module/modm-driver-sk9822 @@ -2788,6 +2902,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:stts22h`]: https://modm.io/reference/module/modm-driver-stts22h [`modm:driver:stusb4500`]: https://modm.io/reference/module/modm-driver-stusb4500 [`modm:driver:sx1276`]: https://modm.io/reference/module/modm-driver-sx1276 +[`modm:driver:sx128x`]: https://modm.io/reference/module/modm-driver-sx128x [`modm:driver:tlc594x`]: https://modm.io/reference/module/modm-driver-tlc594x [`modm:driver:tmp12x`]: https://modm.io/reference/module/modm-driver-tmp12x [`modm:driver:touch2046`]: https://modm.io/reference/module/modm-driver-touch2046 @@ -2806,7 +2921,17 @@ Please note that contributions from xpcc were continuously ported to modm. [#1036]: https://github.com/modm-io/modm/pull/1036 [#1037]: https://github.com/modm-io/modm/pull/1037 [#1038]: https://github.com/modm-io/modm/pull/1038 +[#1040]: https://github.com/modm-io/modm/pull/1040 [#1044]: https://github.com/modm-io/modm/pull/1044 +[#1048]: https://github.com/modm-io/modm/pull/1048 +[#1049]: https://github.com/modm-io/modm/pull/1049 +[#1050]: https://github.com/modm-io/modm/pull/1050 +[#1051]: https://github.com/modm-io/modm/pull/1051 +[#1053]: https://github.com/modm-io/modm/pull/1053 +[#1054]: https://github.com/modm-io/modm/pull/1054 +[#1063]: https://github.com/modm-io/modm/pull/1063 +[#1066]: https://github.com/modm-io/modm/pull/1066 +[#1088]: https://github.com/modm-io/modm/pull/1088 [#118]: https://github.com/modm-io/modm/pull/118 [#122]: https://github.com/modm-io/modm/pull/122 [#132]: https://github.com/modm-io/modm/pull/132 @@ -3020,6 +3145,7 @@ Please note that contributions from xpcc were continuously ported to modm. [0217a19]: https://github.com/modm-io/modm/commit/0217a19 [022a60a]: https://github.com/modm-io/modm/commit/022a60a [0259ad2]: https://github.com/modm-io/modm/commit/0259ad2 +[027811f]: https://github.com/modm-io/modm/commit/027811f [02b1571]: https://github.com/modm-io/modm/commit/02b1571 [038657c]: https://github.com/modm-io/modm/commit/038657c [04688bc]: https://github.com/modm-io/modm/commit/04688bc @@ -3079,10 +3205,12 @@ Please note that contributions from xpcc were continuously ported to modm. [4a82a94]: https://github.com/modm-io/modm/commit/4a82a94 [4ab28fe]: https://github.com/modm-io/modm/commit/4ab28fe [4ce1a47]: https://github.com/modm-io/modm/commit/4ce1a47 +[4d69227]: https://github.com/modm-io/modm/commit/4d69227 [4f25cdf]: https://github.com/modm-io/modm/commit/4f25cdf [4f50d00]: https://github.com/modm-io/modm/commit/4f50d00 [4ff604f]: https://github.com/modm-io/modm/commit/4ff604f [516b2b3]: https://github.com/modm-io/modm/commit/516b2b3 +[517bd84]: https://github.com/modm-io/modm/commit/517bd84 [5332765]: https://github.com/modm-io/modm/commit/5332765 [544f6d3]: https://github.com/modm-io/modm/commit/544f6d3 [55d5911]: https://github.com/modm-io/modm/commit/55d5911 @@ -3091,6 +3219,7 @@ Please note that contributions from xpcc were continuously ported to modm. [596eafa]: https://github.com/modm-io/modm/commit/596eafa [599e0ba]: https://github.com/modm-io/modm/commit/599e0ba [5a9ad25]: https://github.com/modm-io/modm/commit/5a9ad25 +[5d03d53]: https://github.com/modm-io/modm/commit/5d03d53 [5dcdf1d]: https://github.com/modm-io/modm/commit/5dcdf1d [5dd598c]: https://github.com/modm-io/modm/commit/5dd598c [6057873]: https://github.com/modm-io/modm/commit/6057873 @@ -3115,6 +3244,8 @@ Please note that contributions from xpcc were continuously ported to modm. [7d1f7cc]: https://github.com/modm-io/modm/commit/7d1f7cc [7d7490d]: https://github.com/modm-io/modm/commit/7d7490d [7df2e7d]: https://github.com/modm-io/modm/commit/7df2e7d +[7f30961]: https://github.com/modm-io/modm/commit/7f30961 +[8012d82]: https://github.com/modm-io/modm/commit/8012d82 [8082f69]: https://github.com/modm-io/modm/commit/8082f69 [80a9c66]: https://github.com/modm-io/modm/commit/80a9c66 [80ed738]: https://github.com/modm-io/modm/commit/80ed738 @@ -3151,6 +3282,7 @@ Please note that contributions from xpcc were continuously ported to modm. [9e285db]: https://github.com/modm-io/modm/commit/9e285db [9e50a16]: https://github.com/modm-io/modm/commit/9e50a16 [9e7ec34]: https://github.com/modm-io/modm/commit/9e7ec34 +[a05cc62]: https://github.com/modm-io/modm/commit/a05cc62 [a105072]: https://github.com/modm-io/modm/commit/a105072 [a173bde]: https://github.com/modm-io/modm/commit/a173bde [a38feca]: https://github.com/modm-io/modm/commit/a38feca @@ -3173,6 +3305,7 @@ Please note that contributions from xpcc were continuously ported to modm. [b721551]: https://github.com/modm-io/modm/commit/b721551 [b78acd5]: https://github.com/modm-io/modm/commit/b78acd5 [b8648be]: https://github.com/modm-io/modm/commit/b8648be +[ba23833]: https://github.com/modm-io/modm/commit/ba23833 [ba61a34]: https://github.com/modm-io/modm/commit/ba61a34 [bfafcd3]: https://github.com/modm-io/modm/commit/bfafcd3 [c0a8c51]: https://github.com/modm-io/modm/commit/c0a8c51 @@ -3186,6 +3319,7 @@ Please note that contributions from xpcc were continuously ported to modm. [c868f59]: https://github.com/modm-io/modm/commit/c868f59 [c93dd2c]: https://github.com/modm-io/modm/commit/c93dd2c [c949daf]: https://github.com/modm-io/modm/commit/c949daf +[c9e5227]: https://github.com/modm-io/modm/commit/c9e5227 [cb82eec]: https://github.com/modm-io/modm/commit/cb82eec [cbbf3f6]: https://github.com/modm-io/modm/commit/cbbf3f6 [cbce428]: https://github.com/modm-io/modm/commit/cbce428 diff --git a/docs/release/2023q3.md b/docs/release/2023q3.md new file mode 100644 index 0000000000..2d201c560a --- /dev/null +++ b/docs/release/2023q3.md @@ -0,0 +1,106 @@ +## 2023-10-01: 2023q3 release + +This release covers everything from 2023-07-01 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Features: + +- STM32G0 FD-CAN driver. +- Redirect printf to logger in BSPs that support it. + +Integrated Projects: + +- CMSIS-DSP upgraded to v1.15.0. +- ETL upgraded to v20.38.2. +- LVGL upgraded to v8.3.10. +- STM32C0 headers upgraded to v1.1.0. +- STM32H5 headers upgraded to v1.1.0. +- STM32L4 headers upgraded to v1.7.3. +- STM32U5 headers upgraded to v1.3.0. + +Fixes: + +- Place `.data` section in DMA-able memory on STM32H7. +- Correct identification register indices for HMC58x3 drivers. +- Fix spurious EXTI interrupt with shared IRQ. + +New device drivers: + +- IIM-42652 IMU family driver as `modm:driver:ixm42xxx`. +- Stacked SPI Flash driver as `modm:driver:block.device:spi.stack.flash`. +- SX128x LoRa driver as `modm:driver:sx128x`. +- QMC5883L magnetometer driver as `modm:driver:qmc5883l`. + +Known bugs: + +- STM32F7: D-Cache not enabled by default. See #485. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See #285. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See #310. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See #591. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Alexander Solovets (@mbait) 🎉 +- Christopher Durand (@chris-durand) +- Klaus Schnass (@klsc-zeat) 🎉 +- Niklas Hauser (@salkinium) +- Raphael Lehmann (@rleh) +- Rasmus Kleist (@rasmuskleist) + +PR #1088 -> 2023q3. + +
+Detailed changelog + +#### 2023-09-11: Add QMC5883L magnetometer driver + +PR #1063 -> 7f30961. +Tested in hardware by @mbait. + +#### 2023-09-02: Fix EXTI interrupts + +PR #1066 -> c9e5227. +Tested in hardware by @chris-durand. + +#### 2023-08-15: Add SX128x LoRa driver + +PR #1050 -> 517bd84. +Tested in hardware by @rasmuskleist. + +#### 2023-08-13: Add Stacked SPI Flash driver + +PR #1054 -> ba23833. +Tested in hardware by @rasmuskleist. + +#### 2023-07-27: Add ADC driver for STM32H7 + +Also adds support for injected conversions. + +PR #1049 -> 4d69227. +Tested in hardware by @chris-durand. + +#### 2023-07-26: Fix ADC driver for STM32G0 + +PR #1053 -> 5d03d53. +Tested in hardware by @chris-durand. + +#### 2023-07-21: Add CAN driver for STM32G0 + +PR #1051 -> a05cc62. +Tested in hardware by @klsc-zeat. + +#### 2023-07-19: Add IIM-42652 IMU driver + +PR #1040 -> 8012d82. +Tested in hardware by @rasmuskleist. + +#### 2023-07-10: Place `.data` section into D1_SRAM on STM32H7 + +PR #1048 -> 027811f. +Tested in hardware by @chris-durand. + +
diff --git a/tools/scripts/authors.py b/tools/scripts/authors.py index af357d7fa2..6cb1dcad9d 100755 --- a/tools/scripts/authors.py +++ b/tools/scripts/authors.py @@ -89,6 +89,8 @@ "Vivien Henry": "lukh", "Zawadniak Pedro": "PDR5", "Álan Crístoffer": "acristoffers", + "Klaus Schnass": "klsc-zeat", + "Alexander Solovets": "mbait", } def get_author_log(since = None, until = None, handles = False, count = False): From e310746452cc5c93ae2606a8f826b7a24d72c963 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 16:54:40 +0200 Subject: [PATCH 064/159] [platform] Move common SpiMaster acquire()/release() out of drivers --- src/modm/architecture/interface/spi_lock.hpp | 84 +++++++++++++++++++ src/modm/architecture/module.lb | 1 + .../spi/at90_tiny_mega/spi_master.cpp.in | 49 +---------- .../spi/at90_tiny_mega/spi_master.hpp.in | 19 ++--- .../uart_spi_master.cpp.in | 54 +----------- .../uart_spi_master.hpp.in | 20 ++--- .../spi/bitbang/bitbang_spi_master.hpp | 15 +--- .../spi/bitbang/bitbang_spi_master_impl.hpp | 47 ----------- src/modm/platform/spi/rp/spi_master.cpp.in | 33 -------- src/modm/platform/spi/rp/spi_master.hpp.in | 14 +--- src/modm/platform/spi/sam/spi_master.cpp.in | 35 -------- src/modm/platform/spi/sam/spi_master.hpp.in | 13 +-- .../platform/spi/sam_x7x/spi_master.cpp.in | 33 -------- .../platform/spi/sam_x7x/spi_master.hpp.in | 12 +-- src/modm/platform/spi/stm32/spi_master.cpp.in | 33 -------- src/modm/platform/spi/stm32/spi_master.hpp.in | 15 +--- .../spi/stm32_uart/uart_spi_master.cpp.in | 41 --------- .../spi/stm32_uart/uart_spi_master.hpp.in | 12 +-- 18 files changed, 117 insertions(+), 413 deletions(-) create mode 100644 src/modm/architecture/interface/spi_lock.hpp diff --git a/src/modm/architecture/interface/spi_lock.hpp b/src/modm/architecture/interface/spi_lock.hpp new file mode 100644 index 0000000000..bdf630c860 --- /dev/null +++ b/src/modm/architecture/interface/spi_lock.hpp @@ -0,0 +1,84 @@ +/* +* Copyright (c) 2014-2017, Niklas Hauser +* Copyright (c) 2023, Christopher Durand +* +* 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_INTERFACE_SPI_LOCK_HPP +#define MODM_INTERFACE_SPI_LOCK_HPP + +#include +#include + +namespace modm +{ + +/** + * Helper class to implement the synchronization mechanism of @ref modm::SpiMaster + * in SPI device drivers. + * + * Inherit from this class publicly and pass the derived class as the template + * argument, as in `class SpiMaster1 : public modm::SpiLock`. + * + * @tparam Derived SPI master derived class + * @ingroup modm_architecture_spi + */ +template +class SpiLock +{ +public: + static uint8_t + acquire(void* ctx, Spi::ConfigurationHandler handler); + + static uint8_t + release(void* ctx); + +private: + static inline uint8_t count{0}; + static inline void* context{nullptr}; + static inline Spi::ConfigurationHandler configuration{nullptr}; +}; + +template +uint8_t +SpiLock::acquire(void* ctx, Spi::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; +} + +template +uint8_t +SpiLock::release(void* ctx) +{ + if (ctx == context) + { + if (--count == 0) + context = nullptr; + } + return count; +} + +} // namespace modm + +#endif // MODM_INTERFACE_SPI_LOCK_HPP diff --git a/src/modm/architecture/module.lb b/src/modm/architecture/module.lb index 7ce177c87f..09a093a0c0 100644 --- a/src/modm/architecture/module.lb +++ b/src/modm/architecture/module.lb @@ -310,6 +310,7 @@ class Spi(Module): env.outbasepath = "modm/src/modm/architecture" env.copy("interface/spi.hpp") env.copy("interface/spi_master.hpp") + env.copy("interface/spi_lock.hpp") # ----------------------------------------------------------------------------- class SpiDevice(Module): diff --git a/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in b/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in index 8c34ac0110..b93ecdf57d 100644 --- a/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in +++ b/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in @@ -19,19 +19,6 @@ #include #include -// bit 7 (0x80) is used for transfer 1 byte -// bit 6 (0x40) is used for transfer multiple byte -// bit 5-0 (0x3f) are used to store the acquire count -uint8_t -modm::platform::SpiMaster{{ id }}::state(0); - -void * -modm::platform::SpiMaster{{ id }}::context(nullptr); - -modm::Spi::ConfigurationHandler -modm::platform::SpiMaster{{ id }}::configuration(nullptr); -// ---------------------------------------------------------------------------- - void modm::platform::SpiMaster{{ id }}::initialize(Prescaler prescaler) { @@ -39,42 +26,8 @@ modm::platform::SpiMaster{{ id }}::initialize(Prescaler prescaler) SPCR{{ id }} = (1 << SPE{{ id }}) | (1 << MSTR{{ id }}) | (static_cast(prescaler) & ~0x80); SPSR{{ id }} = (static_cast(prescaler) & 0x80) ? (1 << SPI2X{{ id }}) : 0; - state &= 0x3f; + state = 0; } -// ---------------------------------------------------------------------------- - -uint8_t -modm::platform::SpiMaster{{ id }}::acquire(void *ctx, ConfigurationHandler handler) -{ - if (context == nullptr) - { - context = ctx; - state = (state & ~0x3f) | 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 (++state & 0x3f); - - return 0; -} - -uint8_t -modm::platform::SpiMaster{{ id }}::release(void *ctx) -{ - if (ctx == context) - { - if ((--state & 0x3f) == 0) - context = nullptr; - } - return (state & 0x3f); -} -// ---------------------------------------------------------------------------- modm::ResumableResult modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) diff --git a/src/modm/platform/spi/at90_tiny_mega/spi_master.hpp.in b/src/modm/platform/spi/at90_tiny_mega/spi_master.hpp.in index 317850538e..469547490b 100644 --- a/src/modm/platform/spi/at90_tiny_mega/spi_master.hpp.in +++ b/src/modm/platform/spi/at90_tiny_mega/spi_master.hpp.in @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -50,11 +51,8 @@ namespace platform * @ingroup modm_platform_spi modm_platform_spi_{{id}} * @author Niklas Hauser */ -class SpiMaster{{ id }} : public ::modm::SpiMaster, private Spi +class SpiMaster{{ id }} : public ::modm::SpiMaster, public SpiLock, private Spi { - static uint8_t state; - static void *context; - static ConfigurationHandler configuration; public: /// Spi Data Mode, Mode0 is the most common mode enum class @@ -120,14 +118,6 @@ public: } } - - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void *ctx); - - static uint8_t transferBlocking(uint8_t data) { @@ -158,6 +148,11 @@ public: protected: static void initialize(Prescaler prescaler); + +private: + // bit 7 (0x80) is used for transfer 1 byte + // bit 6 (0x40) is used for transfer multiple byte + static inline uint8_t state{}; }; } // namespace platform diff --git a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in index 4801a690aa..e7af684dc8 100644 --- a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in +++ b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in @@ -21,24 +21,6 @@ #include #include -%% if not extended -modm::platform::UartSpiMaster{{id}}::DataOrder -modm::platform::UartSpiMaster{{id}}::dataOrder = DataOrder::MsbFirst; -%% endif - -// bit 7 (0x80) is used for transfer 1 byte -// bit 6 (0x40) is used for transfer multiple byte -// bit 5-0 (0x3f) are used to store the acquire count -uint8_t -modm::platform::UartSpiMaster{{id}}::state(0); - -void * -modm::platform::UartSpiMaster{{id}}::context(nullptr); - -modm::Spi::ConfigurationHandler -modm::platform::UartSpiMaster{{id}}::configuration(nullptr); -// ---------------------------------------------------------------------------- - void modm::platform::UartSpiMaster{{id}}::initialize(uint16_t prescaler) { @@ -71,43 +53,9 @@ modm::platform::UartSpiMaster{{id}}::initialize(uint16_t prescaler) %% if not extended dataOrder = DataOrder::MsbFirst; %% endif - state &= 0x3f; -} -// ---------------------------------------------------------------------------- - -uint8_t -modm::platform::UartSpiMaster{{id}}::acquire(void *ctx, ConfigurationHandler handler) -{ - if (context == nullptr) - { - context = ctx; - state = (state & ~0x3f) | 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 (++state & 0x3f); - - return 0; + state = 0; } -uint8_t -modm::platform::UartSpiMaster{{id}}::release(void *ctx) -{ - if (ctx == context) - { - if ((--state & 0x3f) == 0) - context = nullptr; - } - return (state & 0x3f); -} -// ---------------------------------------------------------------------------- - modm::ResumableResult modm::platform::UartSpiMaster{{id}}::transfer(uint8_t data) { diff --git a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in index cdd8a62fb4..cbbfd22d15 100644 --- a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in +++ b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -32,11 +33,8 @@ namespace platform * @ingroup modm_platform_uart_spi modm_platform_uart_spi_{{id}} * @author Niklas Hauser */ -class UartSpiMaster{{ id }} : public ::modm::SpiMaster +class UartSpiMaster{{ id }} : public ::modm::SpiMaster, public SpiLock { - static uint8_t state; - static void *context; - static ConfigurationHandler configuration; public: /// Spi Data Mode, Mode0 is the most common mode %% if not extended @@ -144,14 +142,6 @@ public: %% endif } - - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void *ctx); - - static uint8_t transferBlocking(uint8_t data) { @@ -176,8 +166,12 @@ protected: initialize(uint16_t prescaler); private: + // bit 7 (0x80) is used for transfer 1 byte + // bit 6 (0x40) is used for transfer multiple byte + static inline uint8_t state{}; + %% if not extended - static DataOrder dataOrder; + static inline DataOrder dataOrder = DataOrder::MsbFirst; %% endif }; diff --git a/src/modm/platform/spi/bitbang/bitbang_spi_master.hpp b/src/modm/platform/spi/bitbang/bitbang_spi_master.hpp index 4cc6496bea..6de402fd96 100644 --- a/src/modm/platform/spi/bitbang/bitbang_spi_master.hpp +++ b/src/modm/platform/spi/bitbang/bitbang_spi_master.hpp @@ -16,7 +16,8 @@ #ifndef MODM_SOFTWARE_BITBANG_SPI_MASTER_HPP #define MODM_SOFTWARE_BITBANG_SPI_MASTER_HPP -#include +#include +#include #include #include #include @@ -41,7 +42,8 @@ namespace platform template< typename Sck, typename Mosi, typename Miso = GpioUnused > -class BitBangSpiMaster : public ::modm::SpiMaster +class BitBangSpiMaster : public ::modm::SpiMaster, + public SpiLock> { public: template< class... Signals > @@ -59,21 +61,12 @@ class BitBangSpiMaster : public ::modm::SpiMaster static void setDataOrder(DataOrder order); - - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void *ctx); - - static uint8_t transferBlocking(uint8_t data); static void transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length); - static modm::ResumableResult transfer(uint8_t data); diff --git a/src/modm/platform/spi/bitbang/bitbang_spi_master_impl.hpp b/src/modm/platform/spi/bitbang/bitbang_spi_master_impl.hpp index 140576ac54..8c8c418210 100644 --- a/src/modm/platform/spi/bitbang/bitbang_spi_master_impl.hpp +++ b/src/modm/platform/spi/bitbang/bitbang_spi_master_impl.hpp @@ -26,17 +26,6 @@ template uint8_t modm::platform::BitBangSpiMaster::operationMode(0); -template -uint8_t -modm::platform::BitBangSpiMaster::count(0); - -template -void * -modm::platform::BitBangSpiMaster::context(nullptr); - -template -modm::Spi::ConfigurationHandler -modm::platform::BitBangSpiMaster::configuration(nullptr); // ---------------------------------------------------------------------------- template @@ -90,42 +79,6 @@ modm::platform::BitBangSpiMaster::setDataOrder(DataOrder order) else operationMode &= ~0b100; } -// ---------------------------------------------------------------------------- - -template -uint8_t -modm::platform::BitBangSpiMaster::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; -} - -template -uint8_t -modm::platform::BitBangSpiMaster::release(void *ctx) -{ - if (ctx == context) - { - if (--count == 0) - context = nullptr; - } - return count; -} -// ---------------------------------------------------------------------------- template uint8_t diff --git a/src/modm/platform/spi/rp/spi_master.cpp.in b/src/modm/platform/spi/rp/spi_master.cpp.in index 74997c31fc..622f3c2e74 100644 --- a/src/modm/platform/spi/rp/spi_master.cpp.in +++ b/src/modm/platform/spi/rp/spi_master.cpp.in @@ -25,39 +25,6 @@ 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) { diff --git a/src/modm/platform/spi/rp/spi_master.hpp.in b/src/modm/platform/spi/rp/spi_master.hpp.in index 86b0590539..e8676bc22b 100644 --- a/src/modm/platform/spi/rp/spi_master.hpp.in +++ b/src/modm/platform/spi/rp/spi_master.hpp.in @@ -10,6 +10,7 @@ // ---------------------------------------------------------------------------- #pragma once +#include #include #include #include @@ -25,18 +26,13 @@ namespace modm::platform * @author Andrey Kunitsyn * @ingroup modm_platform_spi modm_platform_spi_{{id}} */ -class SpiMaster{{ id }} : public modm::SpiMaster +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock { protected: // Bit0: single transfer state // Bit1: block transfer state static inline uint8_t state{0}; -private: - 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); } @@ -168,12 +164,6 @@ public: 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) { diff --git a/src/modm/platform/spi/sam/spi_master.cpp.in b/src/modm/platform/spi/sam/spi_master.cpp.in index 1630e3d0a8..1c5991ce07 100644 --- a/src/modm/platform/spi/sam/spi_master.cpp.in +++ b/src/modm/platform/spi/sam/spi_master.cpp.in @@ -17,41 +17,6 @@ #include "spi_master_{{id}}.hpp" -// ---------------------------------------------------------------------------- - -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) { diff --git a/src/modm/platform/spi/sam/spi_master.hpp.in b/src/modm/platform/spi/sam/spi_master.hpp.in index 98dbffeb08..ed5726dd46 100644 --- a/src/modm/platform/spi/sam/spi_master.hpp.in +++ b/src/modm/platform/spi/sam/spi_master.hpp.in @@ -10,6 +10,7 @@ // ---------------------------------------------------------------------------- #pragma once +#include #include #include #include @@ -23,13 +24,11 @@ namespace modm::platform { * @author Jeff McBride * @ingroup modm_platform_spi modm_platform_spi_{{id}} */ -class SpiMaster{{ id }} : public modm::SpiMaster { +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock +{ // 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}; // Work around a name collision between 'struct modm::Spi' and 'struct Spi' // defined in the CMSIS headers @@ -115,12 +114,6 @@ public: 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)); diff --git a/src/modm/platform/spi/sam_x7x/spi_master.cpp.in b/src/modm/platform/spi/sam_x7x/spi_master.cpp.in index e32b7b2995..218c7dc44d 100644 --- a/src/modm/platform/spi/sam_x7x/spi_master.cpp.in +++ b/src/modm/platform/spi/sam_x7x/spi_master.cpp.in @@ -17,39 +17,6 @@ #include "spi_master_{{id}}.hpp" -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) { diff --git a/src/modm/platform/spi/sam_x7x/spi_master.hpp.in b/src/modm/platform/spi/sam_x7x/spi_master.hpp.in index 597ca59293..9f40cc092f 100644 --- a/src/modm/platform/spi/sam_x7x/spi_master.hpp.in +++ b/src/modm/platform/spi/sam_x7x/spi_master.hpp.in @@ -18,6 +18,7 @@ #ifndef MODM_SAMX7X_SPI_MASTER{{ id }}_HPP #define MODM_SAMX7X_SPI_MASTER{{ id }}_HPP +#include #include #include #include @@ -33,15 +34,12 @@ namespace modm::platform * * @ingroup modm_platform_spi modm_platform_spi_{{id}} */ -class SpiMaster{{ id }} : public modm::SpiMaster, public SpiBase +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock, public SpiBase { private: // 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}; public: using DataMode = SpiBase::DataMode; using Hal = SpiHal{{ id }}; @@ -95,12 +93,6 @@ public: SpiHal{{ id }}::setDataSize(size); } - static uint8_t - acquire(void* ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void* ctx); - static uint8_t transferBlocking(uint8_t data) { diff --git a/src/modm/platform/spi/stm32/spi_master.cpp.in b/src/modm/platform/spi/stm32/spi_master.cpp.in index 57dae48e59..825fc911fe 100644 --- a/src/modm/platform/spi/stm32/spi_master.cpp.in +++ b/src/modm/platform/spi/stm32/spi_master.cpp.in @@ -16,39 +16,6 @@ #include "spi_master_{{id}}.hpp" -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) { diff --git a/src/modm/platform/spi/stm32/spi_master.hpp.in b/src/modm/platform/spi/stm32/spi_master.hpp.in index 87999cfa98..bb94cee464 100644 --- a/src/modm/platform/spi/stm32/spi_master.hpp.in +++ b/src/modm/platform/spi/stm32/spi_master.hpp.in @@ -18,6 +18,7 @@ #ifndef MODM_STM32_SPI_MASTER{{ id }}_HPP #define MODM_STM32_SPI_MASTER{{ id }}_HPP +#include #include #include #include @@ -37,17 +38,13 @@ namespace platform * @author Niklas Hauser * @ingroup modm_platform_spi modm_platform_spi_{{id}} */ -class SpiMaster{{ id }} : public modm::SpiMaster +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock { protected: // `state` must be protected to allow access from SpiMasterDma subclass // Bit0: single transfer state // Bit1: block transfer state static inline uint8_t state{0}; -private: - static inline uint8_t count{0}; - static inline void* context{nullptr}; - static inline ConfigurationHandler configuration{nullptr}; public: using Hal = SpiHal{{ id }}; @@ -127,14 +124,6 @@ public: SpiHal{{ id }}::enableTransfer(); } - - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void *ctx); - - static uint8_t transferBlocking(uint8_t data) { diff --git a/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in b/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in index fa234bf778..282303cd39 100644 --- a/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in +++ b/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in @@ -23,47 +23,6 @@ modm::platform::UartSpiMaster{{ id }}::DataOrder uint8_t modm::platform::UartSpiMaster{{ id }}::state(0); -uint8_t -modm::platform::UartSpiMaster{{ id }}::count(0); - -void * -modm::platform::UartSpiMaster{{ id }}::context(nullptr); - -modm::Spi::ConfigurationHandler -modm::platform::UartSpiMaster{{ id }}::configuration(nullptr); -// ---------------------------------------------------------------------------- - -uint8_t -modm::platform::UartSpiMaster{{ 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::UartSpiMaster{{ id }}::release(void *ctx) -{ - if (ctx == context) - { - if (--count == 0) - context = nullptr; - } - return count; -} // ---------------------------------------------------------------------------- modm::ResumableResult diff --git a/src/modm/platform/spi/stm32_uart/uart_spi_master.hpp.in b/src/modm/platform/spi/stm32_uart/uart_spi_master.hpp.in index 504e8d49f1..9915fc19cb 100644 --- a/src/modm/platform/spi/stm32_uart/uart_spi_master.hpp.in +++ b/src/modm/platform/spi/stm32_uart/uart_spi_master.hpp.in @@ -14,6 +14,7 @@ #ifndef MODM_STM32_UART_SPI_MASTER{{ id }}_HPP #define MODM_STM32_UART_SPI_MASTER{{ id }}_HPP +#include #include #include #include "../uart/uart_hal_{{ id }}.hpp" @@ -33,7 +34,8 @@ namespace platform * @author Niklas Hauser * @ingroup modm_platform_uart_spi modm_platform_uart_spi_{{id}} */ -class UartSpiMaster{{ id }} : public modm::SpiMaster, public UartBase +class UartSpiMaster{{ id }} : public modm::SpiMaster, public UartBase, + public SpiLock { static DataOrder dataOrder; static uint8_t state; @@ -103,14 +105,6 @@ public: dataOrder = order; } - - static uint8_t - acquire(void *ctx, ConfigurationHandler handler = nullptr); - - static uint8_t - release(void *ctx); - - static uint8_t transferBlocking(uint8_t data) { From 0b0ddd4f4b73bfec5f24d4ce705f272c85320035 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 20:22:02 +0200 Subject: [PATCH 065/159] [stm32] Add interface to query DMA transfer status without interrupt --- src/modm/platform/dma/stm32/dma.hpp.in | 50 ++++++++++++++++++--- src/modm/platform/dma/stm32/dma_base.hpp.in | 8 ++-- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index adec14cc0e..9da23cf00b 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -3,7 +3,7 @@ * Copyright (c) 2014-2017, Niklas Hauser * Copyright (c) 2020, Mike Wolfram * Copyright (c) 2021, Raphael Lehmann - * Copyright (c) 2021, Christopher Durand + * Copyright (c) 2021-2023, Christopher Durand * * This file is part of the modm project. * @@ -116,6 +116,10 @@ public: using ChannelHal = DmaChannelHal; +%% if dmaType in ["stm32-stream-channel", "stm32-mux-stream"] + static constexpr uint8_t FlagOffsetLut[8] = {0, 6, 16, 22, 0+32, 6+32, 16+32, 22+32}; +%% endif + public: /** * Configure the DMA channel @@ -145,7 +149,7 @@ public: } /** - * Start the transfer of the DMA channel + * Start the transfer of the DMA channel and clear all interrupt flags. */ static void start() @@ -321,15 +325,14 @@ public: uint32_t(InterruptFlags::Error) << (uint32_t(ChannelID) * 4) }; %% elif dmaType in ["stm32-stream-channel", "stm32-mux-stream"] - constexpr uint8_t offsetLut[8] = {0, 6, 16, 22, 0+32, 6+32, 16+32, 22+32}; static const uint64_t HT_Flag { - uint64_t(InterruptFlags::HalfTransferComplete) << offsetLut[uint32_t(ChannelID)] + uint64_t(InterruptFlags::HalfTransferComplete) << FlagOffsetLut[uint32_t(ChannelID)] }; static const uint64_t TC_Flag { - uint64_t(InterruptFlags::TransferComplete) << offsetLut[uint32_t(ChannelID)] + uint64_t(InterruptFlags::TransferComplete) << FlagOffsetLut[uint32_t(ChannelID)] }; static const uint64_t TE_Flag { - uint64_t(InterruptFlags::Error) << offsetLut[uint32_t(ChannelID)] + uint64_t(InterruptFlags::Error) << FlagOffsetLut[uint32_t(ChannelID)] }; %% endif @@ -349,6 +352,41 @@ public: ControlHal::clearInterruptFlags(InterruptFlags::Global, ChannelID); } + /** + * Read channel status flags when channel interrupts are disabled. + * This function is useful to query the transfer state when the use of + * the channel interrupt is not required for the application. + * + * @warning Flags are automatically cleared in the ISR if the channel + * interrupt is enabled or when start() is called. + */ + static InterruptFlags_t + getInterruptFlags() + { + const auto globalFlags = ControlHal::getInterruptFlags(); + const auto mask = static_cast(InterruptFlags::All); +%% if dmaType in ["stm32-channel-request", "stm32-channel", "stm32-mux"] + const auto shift = static_cast(ChannelID) * 4; +%% elif dmaType in ["stm32-stream-channel", "stm32-mux-stream"] + const auto shift = FlagOffsetLut[uint32_t(ChannelID)]; +%% endif + const auto channelFlags = static_cast((globalFlags >> shift) & mask); + return InterruptFlags_t{channelFlags}; + } + + /** + * Clear channel interrupt flags. + * Use only when the channel interrupt is disabled. + * + * @warning Flags are automatically cleared in the ISR if the channel + * interrupt is enabled or when start() is called. + */ + static void + clearInterruptFlags(InterruptFlags_t flags = InterruptFlags::All) + { + ControlHal::clearInterruptFlags(flags, ChannelID); + } + /** * Enable the IRQ vector of the channel * diff --git a/src/modm/platform/dma/stm32/dma_base.hpp.in b/src/modm/platform/dma/stm32/dma_base.hpp.in index 0b1f12a7d8..b2b250f6a0 100644 --- a/src/modm/platform/dma/stm32/dma_base.hpp.in +++ b/src/modm/platform/dma/stm32/dma_base.hpp.in @@ -228,7 +228,7 @@ public: }; %% if dmaType in ["stm32-stream-channel", "stm32-mux-stream"] - enum class InterruptEnable { + enum class InterruptEnable : uint32_t { DirectModeError = DMA_SxCR_DMEIE, TransferError = DMA_SxCR_TEIE, HalfTransfer = DMA_SxCR_HTIE, @@ -236,7 +236,7 @@ public: }; MODM_FLAGS32(InterruptEnable); - enum class InterruptFlags { + enum class InterruptFlags : uint8_t { FifoError = 0b00'0001, DirectModeError = 0b00'0100, Error = 0b00'1000, @@ -247,14 +247,14 @@ public: }; MODM_FLAGS32(InterruptFlags); %% elif dmaType in ["stm32-channel-request", "stm32-channel", "stm32-mux"] - enum class InterruptEnable { + enum class InterruptEnable : uint32_t { TransferComplete = DMA_CCR_TCIE, HalfTransfer = DMA_CCR_HTIE, TransferError = DMA_CCR_TEIE, }; MODM_FLAGS32(InterruptEnable); - enum class InterruptFlags { + enum class InterruptFlags : uint8_t { Global = 0b0001, TransferComplete = 0b0010, HalfTransferComplete = 0b0100, From 3b2530fe8b42f337aa9d2086c2c4397f9cf634bd Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 21:19:14 +0200 Subject: [PATCH 066/159] [board] Fix SPI1, SPI2 and SPI3 clock for Nucleo H723ZG --- src/modm/board/nucleo_h723zg/board.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/modm/board/nucleo_h723zg/board.hpp b/src/modm/board/nucleo_h723zg/board.hpp index 6b3fbd6575..ed286d3422 100644 --- a/src/modm/board/nucleo_h723zg/board.hpp +++ b/src/modm/board/nucleo_h723zg/board.hpp @@ -32,6 +32,7 @@ struct SystemClock { // Max 550MHz static constexpr uint32_t SysClk = 550_MHz; + static constexpr uint32_t Pll1Q = SysClk / 4; // Max 550MHz static constexpr uint32_t Hclk = SysClk / 1; // D1CPRE static constexpr uint32_t Frequency = Hclk; @@ -53,9 +54,9 @@ struct SystemClock static constexpr uint32_t Dac1 = Apb1; - static constexpr uint32_t Spi1 = Apb2; - static constexpr uint32_t Spi2 = Apb1; - static constexpr uint32_t Spi3 = Apb1; + static constexpr uint32_t Spi1 = Pll1Q; + static constexpr uint32_t Spi2 = Pll1Q; + static constexpr uint32_t Spi3 = Pll1Q; static constexpr uint32_t Spi4 = Apb2; static constexpr uint32_t Spi5 = Apb2; static constexpr uint32_t Spi6 = Apb4; @@ -115,9 +116,9 @@ struct SystemClock .range = Rcc::PllInputRange::MHz1_2, .pllM = 4, // 8 MHz / 4 = 2 MHz .pllN = 275, // 2 MHz * 275 = 550 MHz - .pllP = 1, // 500 MHz / 1 = 550 MHz - .pllQ = 2, // 500 MHz / 2 = 275 MHz - .pllR = 2, // 500 MHz / 2 = 275 MHz + .pllP = 1, // 550 MHz / 1 = 550 MHz + .pllQ = 4, // 550 MHz / 4 = 137.5 MHz + .pllR = 2, // 550 MHz / 2 = 275 MHz }; Rcc::enablePll1(Rcc::PllSource::Hse, pllFactors1); Rcc::setFlashLatency(); From 13c1cd2187254526eb64cbc1b23dc6155f2c0b50 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 18 Jul 2023 23:16:05 +0200 Subject: [PATCH 067/159] [processing] Do not require protothread module to use SPI with fibers --- src/modm/platform/spi/at90_tiny_mega/module.lb | 2 +- src/modm/platform/spi/rp/module.lb | 2 +- src/modm/platform/spi/sam/module.lb | 2 +- src/modm/platform/spi/sam_x7x/module.lb | 2 +- src/modm/platform/spi/stm32/module.lb | 2 +- src/modm/platform/spi/stm32_uart/module.lb | 2 +- src/modm/processing/fiber/module.lb | 9 ++++++++- src/modm/processing/resumable/module.lb | 2 +- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/modm/platform/spi/at90_tiny_mega/module.lb b/src/modm/platform/spi/at90_tiny_mega/module.lb index ff52edd2a0..60a006e169 100644 --- a/src/modm/platform/spi/at90_tiny_mega/module.lb +++ b/src/modm/platform/spi/at90_tiny_mega/module.lb @@ -15,7 +15,7 @@ def get_properties(env): device = env[":target"] driver = device.get_driver("spi:avr") properties = {} - properties["use_fiber"] = env.get(":processing:protothread:use_fiber", False) + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) properties["target"] = device.identifier properties["partname"] = device.partname properties["driver"] = driver diff --git a/src/modm/platform/spi/rp/module.lb b/src/modm/platform/spi/rp/module.lb index ba4c4598e8..c072bf9d15 100644 --- a/src/modm/platform/spi/rp/module.lb +++ b/src/modm/platform/spi/rp/module.lb @@ -25,7 +25,7 @@ class Instance(Module): def build(self, env): env.substitutions = { "id": self.instance, - "use_fiber": env.get(":processing:protothread:use_fiber", False) + "use_fiber": env.query(":processing:fiber:__enabled", False) } env.outbasepath = "modm/src/modm/platform/spi" diff --git a/src/modm/platform/spi/sam/module.lb b/src/modm/platform/spi/sam/module.lb index f3509f493b..4c0e7818f4 100644 --- a/src/modm/platform/spi/sam/module.lb +++ b/src/modm/platform/spi/sam/module.lb @@ -14,7 +14,7 @@ def get_properties(env): device = env[":target"] driver = device.get_driver("spi") properties = {} - properties["use_fiber"] = env.get(":processing:protothread:use_fiber", False) + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) properties["target"] = device.identifier properties["features"] = driver["feature"] if "feature" in driver else [] return properties diff --git a/src/modm/platform/spi/sam_x7x/module.lb b/src/modm/platform/spi/sam_x7x/module.lb index a1cd54519c..459b51e5e9 100644 --- a/src/modm/platform/spi/sam_x7x/module.lb +++ b/src/modm/platform/spi/sam_x7x/module.lb @@ -15,7 +15,7 @@ def get_properties(env): device = env[":target"] properties = {} - properties["use_fiber"] = env.get(":processing:protothread:use_fiber", False) + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) properties["target"] = device.identifier return properties diff --git a/src/modm/platform/spi/stm32/module.lb b/src/modm/platform/spi/stm32/module.lb index 2329a5bc84..7d4ee68e92 100644 --- a/src/modm/platform/spi/stm32/module.lb +++ b/src/modm/platform/spi/stm32/module.lb @@ -16,7 +16,7 @@ def get_properties(env): device = env[":target"] driver = device.get_driver("spi") properties = {} - properties["use_fiber"] = env.get(":processing:protothread:use_fiber", False) + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) properties["target"] = device.identifier properties["features"] = driver["feature"] if "feature" in driver else [] return properties diff --git a/src/modm/platform/spi/stm32_uart/module.lb b/src/modm/platform/spi/stm32_uart/module.lb index a2c4942e02..ee10191197 100644 --- a/src/modm/platform/spi/stm32_uart/module.lb +++ b/src/modm/platform/spi/stm32_uart/module.lb @@ -17,7 +17,7 @@ def get_properties(env): "target": device.identifier, "driver": driver, "over8_sampling": ("feature" in driver) and ("over8" in driver["feature"]), - "use_fiber": env.get(":processing:protothread:use_fiber", False), + "use_fiber": env.query(":processing:fiber:__enabled", False) } return properties diff --git a/src/modm/processing/fiber/module.lb b/src/modm/processing/fiber/module.lb index 30bcfca10c..22e09dbe3f 100644 --- a/src/modm/processing/fiber/module.lb +++ b/src/modm/processing/fiber/module.lb @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2020, Erik Henriksson -# Copyright (c) 2021, Christopher Durand +# Copyright (c) 2021-2023, Christopher Durand # Copyright (c) 2021, Niklas Hauser # # This file is part of the modm project. @@ -16,9 +16,16 @@ def init(module): module.name = ":processing:fiber" module.description = FileReader("module.md") +def is_enabled(env): + return env.get(":processing:protothread:use_fiber", False) or \ + not env.has_module(":processing:protothread") def prepare(module, options): module.depends(":processing:timer") + + module.add_query( + EnvironmentQuery(name="__enabled", factory=is_enabled)) + # No ARM64 support yet! return "arm64" not in options[":target"].get_driver("core")["type"] diff --git a/src/modm/processing/resumable/module.lb b/src/modm/processing/resumable/module.lb index eae81c1431..b68e2bee09 100644 --- a/src/modm/processing/resumable/module.lb +++ b/src/modm/processing/resumable/module.lb @@ -29,7 +29,7 @@ def prepare(module, options): def build(env): env.outbasepath = "modm/src/modm/processing/resumable" - if env.get(":processing:protothread:use_fiber", False): + if env.query(":processing:fiber:__enabled", False): env.copy("macros_fiber.hpp", "macros.hpp") env.copy("resumable_fiber.hpp", "resumable.hpp") else: From 53796b08c5989b8f380aa99267f21ece6681bdf4 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 21:48:47 +0200 Subject: [PATCH 068/159] [stm32] Add STM32H7 SPI driver with DMA support --- README.md | 2 +- src/modm/platform/spi/stm32h7/module.lb | 93 +++++ src/modm/platform/spi/stm32h7/spi_base.hpp.in | 204 ++++++++++ src/modm/platform/spi/stm32h7/spi_hal.hpp.in | 209 +++++++++++ .../platform/spi/stm32h7/spi_hal_impl.hpp.in | 353 ++++++++++++++++++ .../platform/spi/stm32h7/spi_master.cpp.in | 159 ++++++++ .../platform/spi/stm32h7/spi_master.hpp.in | 138 +++++++ .../spi/stm32h7/spi_master_dma.hpp.in | 132 +++++++ .../spi/stm32h7/spi_master_dma_impl.hpp.in | 254 +++++++++++++ 9 files changed, 1543 insertions(+), 1 deletion(-) create mode 100644 src/modm/platform/spi/stm32h7/module.lb create mode 100644 src/modm/platform/spi/stm32h7/spi_base.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_hal.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master.cpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in diff --git a/README.md b/README.md index 80bf649f33..2f33b19e22 100644 --- a/README.md +++ b/README.md @@ -454,7 +454,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -○ +✅ ✅ ✅ ✅ diff --git a/src/modm/platform/spi/stm32h7/module.lb b/src/modm/platform/spi/stm32h7/module.lb new file mode 100644 index 0000000000..6056bc2c83 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/module.lb @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2023, Christopher Durand +# +# 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 get_properties(env): + device = env[":target"] + driver = device.get_driver("spi") + properties = {} + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) + properties["target"] = device.identifier + + return properties + +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): + module.depends(":platform:spi") + return True + + def build(self, env): + properties = get_properties(env) + properties["id"] = self.instance + + device = env[":target"] + if device.identifier.family in ["h7"]: + properties["fifo_size"] = 16 if self.instance in (1, 2, 3) else 8 + properties["max_data_bits"] = 32 if self.instance in (1, 2, 3) else 16 + properties["max_crc_bits"] = properties["max_data_bits"] + properties["max_transfer_size"] = 2**16 - 1 # is smaller for some instances on U5 + properties["i2s"] = self.instance in (1, 2, 3, 6) + else: + raise NotImplementedError("Unsupported device") + + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/spi" + + env.template("spi_hal.hpp.in", "spi_hal_{}.hpp".format(self.instance)) + env.template("spi_hal_impl.hpp.in", "spi_hal_{}_impl.hpp".format(self.instance)) + 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:stm32-extended"): + return False + if device.identifier.family not in ["h7"]: + # U5 is not supported yet + return False + + module.depends( + ":architecture:register", + ":architecture:spi", + ":cmsis:device", + ":math:algorithm", + ":platform:gpio", + ":platform:rcc") + + for driver in device.get_all_drivers("spi:stm32-extended"): + for instance in driver["instance"]: + module.add_submodule(Instance(driver, instance)) + + return True + +def build(env): + env.substitutions = get_properties(env) + env.outbasepath = "modm/src/modm/platform/spi" + + env.template("spi_base.hpp.in") diff --git a/src/modm/platform/spi/stm32h7/spi_base.hpp.in b/src/modm/platform/spi/stm32h7/spi_base.hpp.in new file mode 100644 index 0000000000..2c6a1e8480 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_base.hpp.in @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32H7_SPI_BASE_HPP +#define MODM_STM32H7_SPI_BASE_HPP + +#include +#include "../device.hpp" +#include + +namespace modm::platform +{ + +/** + * Base class for the SPI classes + * + * Provides common definitions that do not depend on the specific SPI instance. + * + * @ingroup modm_platform_spi + */ +class SpiBase +{ +public: + enum class + Interrupt : uint32_t + { + RxPacketAvailable = SPI_IER_RXPIE, + TxPacketSpaceAvailable = SPI_IER_TXPIE, + DuplexPacket = SPI_IER_DXPIE, + EndOfTransfer = SPI_IER_EOTIE, + TxTransferFilled = SPI_IER_TXTFIE, + Underrun = SPI_IER_UDRIE, + Overrun = SPI_IER_OVRIE, + CrcError = SPI_IER_CRCEIE, + TiFrameError = SPI_IER_TIFREIE, + ModeFault = SPI_IER_MODFIE, + Reload = SPI_IER_TSERFIE + }; + MODM_FLAGS32(Interrupt); + + enum class + StatusFlag : uint32_t + { + RxPacketAvailable = SPI_SR_RXP, + TxPacketSpaceAvailable = SPI_SR_TXP, + DuplexPacket = SPI_SR_DXP, + EndOfTransfer = SPI_SR_EOT, + TxTransferFilled = SPI_SR_TXTF, + Underrun = SPI_SR_UDR, + Overrun = SPI_SR_OVR, + CrcError = SPI_SR_CRCE, + TiFrameError = SPI_SR_TIFRE, + ModeFault = SPI_SR_MODF, + Reload = SPI_SR_TSERF, + Suspension = SPI_SR_SUSP, + TxTransferComplete = SPI_SR_TXC, + RxFifoLevel1 = SPI_SR_RXPLVL_1, + RxFifoLevel0 = SPI_SR_RXPLVL_0, + RxFifoWordNotEmpty = SPI_SR_RXWNE + }; + MODM_FLAGS32(StatusFlag); + + enum class + MasterSelection : uint32_t + { + Slave = 0, + Master = SPI_CFG2_MASTER, + Mask = Master, + }; + + enum class + DataMode : uint32_t + { + Mode0 = 0b00, ///< clock normal, sample on rising edge + Mode1 = SPI_CFG2_CPHA, ///< clock normal, sample on falling edge + Mode2 = SPI_CFG2_CPOL, ///< clock inverted, sample on falling edge + /// clock inverted, sample on rising edge + Mode3 = SPI_CFG2_CPOL | SPI_CFG2_CPHA, + Mask = Mode3 + }; + + enum class + DataOrder : uint32_t + { + MsbFirst = 0b0, + LsbFirst = SPI_CFG2_LSBFRST, + Mask = LsbFirst, + }; + + enum class + Prescaler : uint32_t + { + Div2 = 0, + Div4 = SPI_CFG1_MBR_0, + Div8 = SPI_CFG1_MBR_1, + Div16 = SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + Div32 = SPI_CFG1_MBR_2, + Div64 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_0, + Div128 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1, + Div256 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0 + }; + + enum class + DataSize : uint32_t + { + Bit4 = 3, + Bit5 = 4, + Bit6 = 5, + Bit7 = 6, + Bit8 = 7, + Bit9 = 8, + Bit10 = 9, + Bit11 = 10, + Bit12 = 11, + Bit13 = 12, + Bit14 = 13, + Bit15 = 14, + Bit16 = 15, + Bit17 = 16, + Bit18 = 17, + Bit19 = 18, + Bit20 = 19, + Bit21 = 20, + Bit22 = 21, + Bit23 = 22, + Bit24 = 23, + Bit25 = 24, + Bit26 = 25, + Bit27 = 26, + Bit28 = 27, + Bit29 = 28, + Bit30 = 29, + Bit31 = 30, + Bit32 = 31 + }; + static_assert(SPI_CFG1_DSIZE_Pos == 0); + + enum class DmaMode : uint32_t + { + None = 0, + Tx = SPI_CFG1_TXDMAEN, + Rx = SPI_CFG1_RXDMAEN, + Mask = SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN + }; + MODM_FLAGS32(DmaMode); + + enum class DuplexMode : uint32_t + { + FullDuplex = 0, + TransmitOnly = SPI_CFG2_COMM_0, + ReceiveOnly = SPI_CFG2_COMM_1, + HalfDuplex = SPI_CFG2_COMM_1 | SPI_CFG2_COMM_0, + Mask = HalfDuplex + }; + + enum class SlaveSelectMode + { + HardwareGpio, + Software = SPI_CFG2_SSM + }; + + enum class SlaveSelectPolarity + { + ActiveLow = 0, + ActiveHigh = SPI_CFG2_SSIOP + }; + + enum class CrcInit : uint32_t + { + AllZeros = 0, + AllOnes = SPI_CR1_TCRCINI | SPI_CR1_RCRCINI, + Mask = AllOnes + }; +}; + +constexpr auto +operator<=>(SpiBase::DataSize s0, SpiBase::DataSize s1) +{ + const auto v0 = static_cast(s0); + const auto v1 = static_cast(s1); + if (v0 < v1) { + return std::strong_ordering::less; + } else if (v0 > v1) { + return std::strong_ordering::greater; + } else { + return std::strong_ordering::equal; + } +} + +} // namespace modm::platform + +#endif // MODM_STM32H7_SPI_BASE_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_hal.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal.hpp.in new file mode 100644 index 0000000000..214cb1d296 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_hal.hpp.in @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2018, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32H7_SPI_HAL{{ id }}_HPP +#define MODM_STM32H7_SPI_HAL{{ id }}_HPP + +#include "spi_base.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}) + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +class SpiHal{{ id }} : public SpiBase +{ +public: + static constexpr auto peripheral = Peripheral::Spi{{ id }}; + + static constexpr DataSize MaxDataBits = DataSize::Bit{{ max_data_bits }}; + static constexpr DataSize MaxCrcBits = DataSize::Bit{{ max_crc_bits }}; + + /// Maximum permitted size for fixed size transfers + static constexpr std::size_t MaxTransferSize = {{ max_transfer_size }}; + + /// Size of FIFO in bytes + static constexpr std::size_t FifoSize = {{ fifo_size }}; + + static void + enableClock(); + + static void + disableClock(); + + static void + initialize(Prescaler prescaler, + MasterSelection masterSelection = MasterSelection::Master, + DataMode dataMode = DataMode::Mode0, + DataOrder dataOrder = DataOrder::MsbFirst, + DataSize dataSize = DataSize::Bit8); + + static void + enableTransfer(); + + static void + disableTransfer(); + + static bool + isTransferEnabled(); + + static void + startMasterTransfer(); + + static void + suspendMasterTransfer(); + + static void + setDataMode(DataMode dataMode); + + static void + setDataOrder(DataOrder dataOrder); + + static void + setDataSize(DataSize dataSize); + + static void + setMasterSelection(MasterSelection masterSelection); + + static void + setDuplexMode(DuplexMode mode); + + static void + setCrcEnabled(bool enabled); + + static void + setCrcSize(DataSize crcSize); + + static void + setCrcPolynomial(uint32_t poly); + + static void + setCrcInitialValue(CrcInit init); + + static void + setDmaMode(DmaMode_t mode); + + /** + * Configure total amount of data items of transfer + * Set size to 0 for unlimited transfers. + * @param reload Next transfer size after reload + */ + static void + setTransferSize(uint16_t size, uint16_t reload = 0); + + static void + setSlaveSelectMode(SlaveSelectMode mode); + + static void + setSlaveSelectPolarity(SlaveSelectPolarity polarity); + + static void + setSlaveSelectState(bool state); + + static uint32_t + transmitCrc(); + + static uint32_t + receiveCrc(); + + static volatile uint32_t* + transmitRegister(); + + static const volatile uint32_t* + receiveRegister(); + + static StatusFlag_t + status(); + + /// @return true if SPI_SR_EOT is set + static bool + isTransferCompleted(); + + /// @return true if SPI_SR_TXC is set + static bool + isTxCompleted(); + + /// @return true if SPI_SR_TXP is not set + static bool + isTxFifoFull(); + + /// @return true if SPI_SR_RXP is set + static bool + isRxDataAvailable(); + + /** + * Write up to 8 Bit to the transmit data register + * + * @warning Writing with a size smaller than the configured data size is not allowed. + */ + static void + write(uint8_t data); + + /** + * Write up to 16 Bit to the data register + * @warning Writing with a size smaller than the configured data size is not allowed. + */ + static void + write16(uint16_t data); + + /** + * Write up to 32 Bit to the transmit data register + */ + static void + write32(uint32_t data); + + /** + * Read an 8-bit value from the receive data register. + * + * @warning Reading with a size smaller than the configured data size is not allowed. + */ + static uint8_t + read(); + + /** + * Read a 16-bit value from the receive data register. + * + * @warning Reading with a size smaller than the configured data size is not allowed. + */ + static uint16_t + read16(); + + /** + * Read a 32-bit value from the receive data register. + */ + static uint32_t + read32(); + + static void + enableInterruptVector(bool enable, uint32_t priority); + + static void + enableInterrupt(Interrupt_t interrupt); + + static void + disableInterrupt(Interrupt_t interrupt); + + static void + acknowledgeInterruptFlags(StatusFlag_t flags); +}; + +} // namespace modm::platform + +#include "spi_hal_{{ id }}_impl.hpp" + +#endif // MODM_STM32H7_SPI_HAL{{ id }}_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in new file mode 100644 index 0000000000..77770cf4bd --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in @@ -0,0 +1,353 @@ +/* +* Copyright (c) 2013, Kevin Läufer +* Copyright (c) 2013-2017, Niklas Hauser +* Copyright (c) 2014, Daniel Krebs +* Copyright (c) 2020, Mike Wolfram +* Copyright (c) 2023, Christopher Durand +* +* 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_STM32H7_SPI_HAL{{ id }}_HPP +# error "Don't include this file directly, use 'spi_hal{{ id }}.hpp' instead!" +#endif +#include + +namespace modm::platform +{ + +inline void +SpiHal{{ id }}::enableClock() +{ + Rcc::enable(); +} + +inline void +SpiHal{{ id }}::disableClock() +{ + Rcc::disable(); +} + +inline void +SpiHal{{ id }}::initialize(Prescaler prescaler, + MasterSelection masterSelection, DataMode dataMode, + DataOrder dataOrder, DataSize dataSize) +{ + enableClock(); + disableTransfer(); + + acknowledgeInterruptFlags( + StatusFlag::EndOfTransfer | + StatusFlag::TxTransferFilled | + StatusFlag::Underrun | + StatusFlag::Overrun | + StatusFlag::CrcError | + StatusFlag::TiFrameError | + StatusFlag::ModeFault | + StatusFlag::Reload | + StatusFlag::Suspension + ); + + // initialize with unlimited transfer size + setTransferSize(0); + + // Pause master transfer if RX FIFO is full + SPI{{ id }}->CR1 = SPI_CR1_MASRX; + + SPI{{ id }}->CFG2 = static_cast(dataMode) + | static_cast(dataOrder) + | static_cast(masterSelection) + | SPI_CFG2_SSOE; // disable multi-master support to prevent spurious mode fault + + SPI{{ id }}->CFG1 = static_cast(prescaler) + | static_cast(dataSize); +} + +inline void +SpiHal{{ id }}::setDataMode(DataMode dataMode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DataMode::Mask)) + | static_cast(dataMode); +} + +inline void +SpiHal{{ id }}::setDataOrder(DataOrder dataOrder) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DataOrder::Mask)) + | static_cast(dataOrder); +} + +inline void +SpiHal{{ id }}::setDataSize(DataSize dataSize) +{ + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~SPI_CFG1_DSIZE_Msk) + | static_cast(dataSize); +} + +inline void +SpiHal{{ id }}::setMasterSelection(MasterSelection masterSelection) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(MasterSelection::Mask)) + | static_cast(masterSelection); +} + +inline void +SpiHal{{ id }}::setDuplexMode(DuplexMode mode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DuplexMode::Mask)) + | static_cast(mode); +} + +inline void +SpiHal{{ id }}::setCrcEnabled(bool enabled) +{ + if (enabled) { + SPI{{ id }}->CFG1 |= SPI_CFG1_CRCEN; + } else { + SPI{{ id }}->CFG1 &= ~SPI_CFG1_CRCEN; + } +} + +inline void +SpiHal{{ id }}::setCrcSize(DataSize crcSize) +{ + if ((crcSize == DataSize::Bit16) or (crcSize == DataSize::Bit32)) { + SPI{{ id }}->CR1 |= SPI_CR1_CRC33_17; + } else { + SPI{{ id }}->CR1 &= ~SPI_CR1_CRC33_17; + } + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~SPI_CFG1_CRCSIZE_Msk) + | (static_cast(crcSize) << SPI_CFG1_CRCSIZE_Pos); +} + +inline void +SpiHal{{ id }}::setCrcPolynomial(uint32_t poly) +{ + SPI{{ id }}->CRCPOLY = poly; +} + +inline void +SpiHal{{ id }}::setCrcInitialValue(CrcInit init) +{ + SPI{{ id }}->CR1 = (SPI{{ id }}->CR1 & ~static_cast(CrcInit::Mask)) | + static_cast(init); +} + +inline void +SpiHal{{ id }}::setTransferSize(uint16_t size, uint16_t reload) +{ + static_assert(SPI_CR2_TSIZE_Pos == 0); + SPI{{ id }}->CR2 = (reload << SPI_CR2_TSER_Pos) | size; +} + +inline void +SpiHal{{ id }}::setDmaMode(DmaMode_t mode) +{ + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~(DmaMode::Tx | DmaMode::Rx).value) + | mode.value; +} + +inline void +SpiHal{{ id }}::setSlaveSelectMode(SlaveSelectMode mode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~SPI_CFG2_SSM_Msk) + | uint32_t(mode); +} + +inline void +SpiHal{{ id }}::setSlaveSelectPolarity(SlaveSelectPolarity polarity) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~SPI_CFG2_SSIOP_Msk) + | uint32_t(polarity); +} + +inline void +SpiHal{{ id }}::setSlaveSelectState(bool state) +{ + if (state) { + SPI{{ id }}->CR1 |= SPI_CR1_SSI; + } else { + SPI{{ id }}->CR1 &= ~SPI_CR1_SSI; + } +} + +inline void +SpiHal{{ id }}::write(uint8_t data) +{ + // Write with 8-bit access + auto* const ptr = reinterpret_cast<__IO uint8_t*>(&SPI{{ id }}->TXDR); + *ptr = data; +} + +inline void +SpiHal{{ id }}::write16(uint16_t data) +{ + // Write with 16-bit access + // SPI{{ id }}->TXDR is of type "volatile uint32_t". + // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. + auto* const [[gnu::may_alias]] ptr = reinterpret_cast<__IO uint16_t*>(&SPI{{ id }}->TXDR); + *ptr = data; +} + +inline void +SpiHal{{ id }}::write32(uint32_t data) +{ + SPI{{ id }}->TXDR = data; +} + +inline uint8_t +SpiHal{{ id }}::read() +{ + // Read with 8-bit access + return *reinterpret_cast(&SPI{{ id }}->RXDR); +} + +inline uint16_t +SpiHal{{ id }}::read16() +{ + // Read with 16-bit access + // SPI{{ id }}->RXDR is of type "const volatile uint32_t". + // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. + auto* const [[gnu::may_alias]] ptr = reinterpret_cast(&SPI{{ id }}->RXDR); + return *ptr; +} + +inline uint32_t +SpiHal{{ id }}::read32() +{ + return SPI{{ id }}->RXDR; +} + +inline void +SpiHal{{ id }}::enableInterruptVector(bool enable, uint32_t priority) +{ + if (enable) { + // Set priority for the interrupt vector + NVIC_SetPriority(SPI{{ id }}_IRQn, priority); + // register IRQ at the NVIC + NVIC_EnableIRQ(SPI{{ id }}_IRQn); + } + else { + NVIC_DisableIRQ(SPI{{ id }}_IRQn); + } +} + +inline void +SpiHal{{ id }}::enableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->IER |= interrupt.value; +} + +inline void +SpiHal{{ id }}::disableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->IER &= ~interrupt.value; +} + +inline void +SpiHal{{ id }}::acknowledgeInterruptFlags(StatusFlag_t flags) +{ + constexpr auto mask = + SPI_IFCR_EOTC | + SPI_IFCR_TXTFC | + SPI_IFCR_UDRC | + SPI_IFCR_OVRC | + SPI_IFCR_CRCEC | + SPI_IFCR_TIFREC | + SPI_IFCR_MODFC | + SPI_IFCR_TSERFC | + SPI_IFCR_SUSPC; + + SPI{{ id }}->IFCR = flags.value & mask; +} + +inline SpiHal{{ id }}::StatusFlag_t +SpiHal{{ id }}::status() +{ + return StatusFlag_t(SPI{{ id }}->SR); +} + +inline bool +SpiHal{{ id }}::isTransferCompleted() +{ + return bool(status() & StatusFlag::EndOfTransfer); +} + +inline bool +SpiHal{{ id }}::isTxCompleted() +{ + return bool(status() & StatusFlag::TxTransferComplete); +} + +inline bool +SpiHal{{ id }}::isTxFifoFull() +{ + return !(status() & StatusFlag::TxPacketSpaceAvailable); +} + +inline bool +SpiHal{{ id }}::isRxDataAvailable() +{ + return bool(status() & StatusFlag::RxPacketAvailable); +} + +inline void +SpiHal{{ id }}::enableTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_SPE; +} + +inline void +SpiHal{{ id }}::disableTransfer() +{ + SPI{{ id }}->CR1 &= ~SPI_CR1_SPE; +} + +inline bool +SpiHal{{ id }}::isTransferEnabled() +{ + return (SPI{{ id }}->CR1 & SPI_CR1_SPE); +} + +inline void +SpiHal{{ id }}::startMasterTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_CSTART; +} + +inline void +SpiHal{{ id }}::suspendMasterTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_CSUSP; +} + +inline uint32_t +SpiHal{{ id }}::transmitCrc() +{ + return SPI{{ id }}->TXCRC; +} + +inline uint32_t +SpiHal{{ id }}::receiveCrc() +{ + return SPI{{ id }}->RXCRC; +} + +inline volatile uint32_t* +SpiHal{{ id }}::transmitRegister() +{ + return &SPI{{ id }}->TXDR; +} + +inline const volatile uint32_t* +SpiHal{{ id }}::receiveRegister() +{ + return &SPI{{ id }}->RXDR; +} + +} // namespace modm::platform diff --git a/src/modm/platform/spi/stm32h7/spi_master.cpp.in b/src/modm/platform/spi/stm32h7/spi_master.cpp.in new file mode 100644 index 0000000000..02b498a617 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master.cpp.in @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2009, Martin Rosekeit +* Copyright (c) 2009-2012, Fabian Greif +* Copyright (c) 2010, Georgi Grinshpun +* Copyright (c) 2012-2017, Niklas Hauser +* Copyright (c) 2013, Kevin Läufer +* Copyright (c) 2014, Sascha Schade +* Copyright (c) 2023, Christopher Durand +* +* 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" + +namespace modm::platform +{ + +modm::ResumableResult +SpiMaster{{ id }}::transfer(uint8_t data) +{ +%% if use_fiber + while (Hal::isTxFifoFull()) + modm::fiber::yield(); + + Hal::write(data); + + while (!Hal::isRxDataAvailable()) + modm::fiber::yield(); + + return Hal::read(); +%% else + // 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. + + if (!(state & Bit0)) + { + // wait for previous transfer to finish + if (Hal::isTxFifoFull()) + return {modm::rf::Running}; + + Hal::write(data); + + // set LSB = Bit0 + state |= Bit0; + } + + if (!Hal::isRxDataAvailable()) + return {modm::rf::Running}; + + data = Hal::read(); + + state &= ~Bit0; + return {modm::rf::Stop, data}; +%% endif +} + +modm::ResumableResult +SpiMaster{{ id }}::transfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ +%% if use_fiber + std::size_t rxIndex = 0; + std::size_t txIndex = 0; + + while (rxIndex < length) { + while ((txIndex < length) and !Hal::isTxFifoFull()) { + Hal::write(tx ? tx[txIndex] : 0); + ++txIndex; + } + while ((rxIndex < length) and Hal::isRxDataAvailable()) { + const uint8_t data = Hal::read(); + if (rx) { + rx[rxIndex] = data; + } + ++rxIndex; + } + if (rxIndex < length) { + modm::fiber::yield(); + } + } +%% else + // 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 transfer to finish. + + // we need to globally remember which byte we are currently transferring + static std::size_t rxIndex = 0; + static std::size_t txIndex = 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 + rxIndex = 0; + txIndex = 0; + while (rxIndex < length) { + default: + { + while ((txIndex < length) and !Hal::isTxFifoFull()) { + Hal::write(tx ? tx[txIndex] : 0); + ++txIndex; + } + while ((rxIndex < length) and Hal::isRxDataAvailable()) { + if (rx) { + rx[rxIndex] = Hal::read(); + } else { + Hal::read(); + } + ++rxIndex; + } + if (rxIndex < length) { + return {modm::rf::Running}; + } + } + } + + // clear the state + state &= ~Bit1; + return {modm::rf::Stop}; + } +%% endif +} + +void +SpiMaster{{ id }}::finishTransfer() +{ + if (Hal::isTransferEnabled()) { + while (!(Hal::status() & Hal::StatusFlag::TxTransferComplete)); + Hal::disableTransfer(); + } + + Hal::acknowledgeInterruptFlags( + Hal::StatusFlag::EndOfTransfer | + Hal::StatusFlag::TxTransferFilled | + Hal::StatusFlag::Underrun | + Hal::StatusFlag::Overrun | + Hal::StatusFlag::CrcError | + Hal::StatusFlag::TiFrameError | + Hal::StatusFlag::ModeFault | + Hal::StatusFlag::Reload | + Hal::StatusFlag::Suspension + ); +} + +} // namespace modm::platform diff --git a/src/modm/platform/spi/stm32h7/spi_master.hpp.in b/src/modm/platform/spi/stm32h7/spi_master.hpp.in new file mode 100644 index 0000000000..f44a0883a9 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master.hpp.in @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2011, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2011-2017, Niklas Hauser + * Copyright (c) 2012, Georgi Grinshpun + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2022-2023, Christopher Durand + * + * 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_STM32H7_SPI_MASTER{{ id }}_HPP +#define MODM_STM32H7_SPI_MASTER{{ id }}_HPP + +#include +#include +#include +#include +%% if use_fiber +#include +%% endif +#include "spi_hal_{{ id }}.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}). + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock +{ +%% if not use_fiber + // Bit0: single transfer state + // Bit1: block transfer state + static inline uint8_t state{0}; +%% endif +public: + using Hal = SpiHal{{ id }}; + + using DataMode = Hal::DataMode; + using DataOrder = Hal::DataOrder; + using DataSize = Hal::DataSize; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Sck = typename Connector::template GetSignal; + using Mosi = typename Connector::template GetSignal; + using Miso = typename Connector::template GetSignal; + + Sck::setOutput(Gpio::OutputType::PushPull); + Mosi::setOutput(Gpio::OutputType::PushPull); + Miso::setInput(Gpio::InputType::Floating); + Connector::connect(); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5) > + static void + initialize() + { + constexpr auto result = modm::Prescaler::from_power(SystemClock::Spi{{ id }}, baudrate, 2, 256); + assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); + + // translate the prescaler into the bitmapping + constexpr Hal::Prescaler prescaler{result.index << SPI_CFG1_MBR_Pos}; +%% if not use_fiber + state = 0; +%% endif + + Hal::initialize(prescaler); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataMode(DataMode mode) + { + finishTransfer(); + Hal::setDataMode(mode); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataOrder(DataOrder order) + { + finishTransfer(); + Hal::setDataOrder(order); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataSize(DataSize size) + { + finishTransfer(); + Hal::setDataSize(static_cast(size)); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + 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); + +private: + static void + finishTransfer(); +}; + +} // namespace modm::platform + +#endif // MODM_STM32H7_SPI_MASTER{{ id }}_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in new file mode 100644 index 0000000000..009fabe238 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32H7_SPI_MASTER{{ id }}_DMA_HPP +#define MODM_STM32H7_SPI_MASTER{{ id }}_DMA_HPP + +#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 + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +template +class SpiMaster{{ id }}_Dma : public modm::SpiMaster, + public SpiLock> +{ +protected: + struct Dma { + using RxChannel = typename DmaChannelRx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Channel; + using TxChannel = typename DmaChannelTx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Channel; + static constexpr DmaBase::Request RxRequest = DmaChannelRx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Request; + static constexpr DmaBase::Request TxRequest = DmaChannelTx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Request; + }; + +%% if not use_fiber + // Bit0: single transfer state + // Bit1: block transfer state + // Bit2: block transfer rx dma transfer completed + static inline uint8_t state{0}; +%% endif +public: + using Hal = SpiHal{{ id }}; + + using DataMode = Hal::DataMode; + using DataOrder = Hal::DataOrder; + using DataSize = Hal::DataSize; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Sck = typename Connector::template GetSignal; + using Mosi = typename Connector::template GetSignal; + using Miso = typename Connector::template GetSignal; + + Sck::setOutput(Gpio::OutputType::PushPull); + Mosi::setOutput(Gpio::OutputType::PushPull); + Miso::setInput(Gpio::InputType::Floating); + Connector::connect(); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5) > + static void + initialize(); + + static void + setDataMode(DataMode mode) + { + Hal::setDataMode(mode); + } + + static void + setDataOrder(DataOrder order) + { + Hal::setDataOrder(order); + } + + static void + setDataSize(DataSize size) + { + Hal::setDataSize(static_cast(size)); + } + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + /// @pre At least one of tx or rx must not be nullptr + 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); + + /// @pre At least one of tx or rx must not be nullptr + static modm::ResumableResult + transfer(const uint8_t* tx, uint8_t* rx, std::size_t length); + +protected: + static void + finishTransfer(); + + static void + startDmaTransfer(const uint8_t* tx, uint8_t* rx, std::size_t length); +}; + +} // namespace modm::platform + +#include "spi_master_{{ id }}_dma_impl.hpp" + +#endif // MODM_STM32_SPI_MASTER{{ id }}_DMA_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in new file mode 100644 index 0000000000..10aa8d2ab2 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32H7_SPI_MASTER{{ id }}_DMA_HPP +# error "Don't include this file directly, use 'spi_master_{{ id }}_dma.hpp' instead!" +#endif + +namespace modm::platform +{ + +template +template +void +SpiMaster{{ id }}_Dma::initialize() +{ + Dma::RxChannel::configure(DmaBase::DataTransferDirection::PeripheralToMemory, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::Medium); + Dma::RxChannel::disableInterruptVector(); + Dma::RxChannel::setPeripheralAddress(reinterpret_cast(Hal::receiveRegister())); + Dma::RxChannel::template setPeripheralRequest(); + + Dma::TxChannel::configure(DmaBase::DataTransferDirection::MemoryToPeripheral, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::Medium); + Dma::TxChannel::disableInterruptVector(); + Dma::TxChannel::setPeripheralAddress(reinterpret_cast(Hal::transmitRegister())); + Dma::TxChannel::template setPeripheralRequest(); + +%% if not use_fiber + state = 0; +%% endif + + constexpr auto result = modm::Prescaler::from_power(SystemClock::Spi{{ id }}, baudrate, 2, 256); + assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); + + constexpr Hal::Prescaler prescaler{result.index << SPI_CFG1_MBR_Pos}; + Hal::initialize(prescaler); +} + +template +modm::ResumableResult +SpiMaster{{ id }}_Dma::transfer(uint8_t data) +{ + // DMA is not used for single byte transfers +%% if use_fiber + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + Hal::setTransferSize(1); + Hal::enableTransfer(); + Hal::write(data); + Hal::startMasterTransfer(); + + // wait for transfer to complete + while (!Hal::isTransferCompleted()) + modm::fiber::yield(); + + data = SpiHal{{ id }}::read(); + finishTransfer(); + + return data; +%% else + // 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) ) + { + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + Hal::setTransferSize(1); + Hal::enableTransfer(); + Hal::write(data); + Hal::startMasterTransfer(); + + // set LSB = Bit0 + state |= Bit0; + } + + if (!Hal::isTransferCompleted()) + return {modm::rf::Running}; + + data = SpiHal{{ id }}::read(); + finishTransfer(); + + // transfer finished + state &= ~Bit0; + return {modm::rf::Stop, data}; +%% endif +} + +template +void +SpiMaster{{ id }}_Dma::startDmaTransfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ + Hal::setTransferSize(length); + + if (tx and rx) { + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + } else if (rx) { + Hal::setDuplexMode(Hal::DuplexMode::ReceiveOnly); + } else { // tx only + Hal::setDuplexMode(Hal::DuplexMode::TransmitOnly); + } + + /* + * Required order of operations according to the reference manual: + * 1. Enable SPI RX DMA + * 2. Enable DMA channels + * 3. Enable SPI TX DMA + * 4. Start transfer + */ + if (rx) { + Dma::RxChannel::setMemoryAddress(reinterpret_cast(rx)); + Dma::RxChannel::setDataLength(length); + Hal::setDmaMode(Hal::DmaMode::Rx); + Dma::RxChannel::start(); + } + + if (tx) { + Dma::TxChannel::setMemoryAddress(reinterpret_cast(tx)); + Dma::TxChannel::setDataLength(length); + Dma::TxChannel::start(); + if (rx) { + Hal::setDmaMode(Hal::DmaMode::Tx | Hal::DmaMode::Rx); + } else { + Hal::setDmaMode(Hal::DmaMode::Tx); + } + } + + Hal::enableTransfer(); + Hal::startMasterTransfer(); +} + + +template +modm::ResumableResult +SpiMaster{{ id }}_Dma::transfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ + using Flags = DmaBase::InterruptFlags; +%% if use_fiber + startDmaTransfer(tx, rx, length); + + bool dmaRxFinished = (rx == nullptr); + while (!Hal::isTransferCompleted() or !dmaRxFinished) { + if(rx) { + const auto flags = DmaChannelRx::getInterruptFlags(); + if (flags & Flags::Error) { + break; + } + if (flags & Flags::TransferComplete) { + dmaRxFinished = true; + } + } + if(tx) { + const auto flags = DmaChannelTx::getInterruptFlags(); + if (flags & Flags::Error) { + break; + } + } + modm::fiber::yield(); + } + finishTransfer(); +%% else + // 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; + startDmaTransfer(tx, rx, length); + if (!rx) { + state |= Bit2; + } + [[fallthrough]]; + + default: + if (!Hal::isTransferCompleted() or !(state & Bit2)) { + if(rx) { + static DmaBase::InterruptFlags_t flags; + flags = DmaChannelRx::getInterruptFlags(); + if (flags & Flags::Error) { + // abort on DMA error + finishTransfer(); + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } + if (flags & Flags::TransferComplete) { + state |= Bit2; + } + } + if(tx) { + if (DmaChannelTx::getInterruptFlags() & Flags::Error) { + // abort on DMA error + finishTransfer(); + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } + } + return { modm::rf::Running }; + } + + finishTransfer(); + + // clear the state + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } +%% endif +} + +template +void +SpiMaster{{ id }}_Dma::finishTransfer() +{ + DmaChannelTx::stop(); + DmaChannelRx::stop(); + + Hal::setDmaMode(Hal::DmaMode::None); + Hal::disableTransfer(); + + Hal::acknowledgeInterruptFlags( + Hal::StatusFlag::EndOfTransfer | + Hal::StatusFlag::TxTransferFilled | + Hal::StatusFlag::Underrun | + Hal::StatusFlag::Overrun | + Hal::StatusFlag::CrcError | + Hal::StatusFlag::TiFrameError | + Hal::StatusFlag::ModeFault | + Hal::StatusFlag::Reload | + Hal::StatusFlag::Suspension + ); +} + +} // namespace modm::platform From f1f4f867ff275af5186fb7dd3cd078f28b926f3e Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 20 Jul 2023 16:56:13 +0200 Subject: [PATCH 069/159] [driver] Add BMI088 driver --- README.md | 27 +- src/modm/driver/inertial/bmi088.hpp | 330 ++++++++++++++++ src/modm/driver/inertial/bmi088.lb | 38 ++ src/modm/driver/inertial/bmi088_impl.hpp | 355 ++++++++++++++++++ src/modm/driver/inertial/bmi088_transport.hpp | 232 ++++++++++++ .../driver/inertial/bmi088_transport_impl.hpp | 188 ++++++++++ 6 files changed, 1157 insertions(+), 13 deletions(-) create mode 100644 src/modm/driver/inertial/bmi088.hpp create mode 100644 src/modm/driver/inertial/bmi088.lb create mode 100644 src/modm/driver/inertial/bmi088_impl.hpp create mode 100644 src/modm/driver/inertial/bmi088_transport.hpp create mode 100644 src/modm/driver/inertial/bmi088_transport_impl.hpp diff --git a/README.md b/README.md index 2f33b19e22..a2ad41c854 100644 --- a/README.md +++ b/README.md @@ -723,96 +723,97 @@ your specific needs. SPI Flash BME280 +BMI088 BMP085 BNO055 CAT24AA CYCLE-COUNTER -DRV832X +DRV832X DS1302 DS1631 DS18B20 EA-DOG Encoder Input -Encoder Input BitBang +Encoder Input BitBang Encoder Output BitBang FT245 FT6x06 Gpio Sampler HCLAx -HD44780 +HD44780 HMC58x HMC6343 HX711 I2C-EEPROM ILI9341 -IS31FL3733 +IS31FL3733 ITG3200 IXM42XXX L3GD20 LAN8720A LAWICEL -LIS302DL +LIS302DL LIS3DSH LIS3MDL LM75 LP503x LSM303A -LSM6DS33 +LSM6DS33 LSM6DSO LTC2984 MAX31855 MAX31865 MAX6966 -MAX7219 +MAX7219 MCP23x17 MCP2515 MCP3008 MCP7941x MCP990X -MMC5603 +MMC5603 MS5611 MS5837 NOKIA5110 NRF24 TFT-DISPLAY -PAT9125EL +PAT9125EL PCA8574 PCA9535 PCA9548A PCA9685 QMC5883L -SH1106 +SH1106 SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 SSD1306 -ST7586S +ST7586S ST7789 STTS22H STUSB4500 SX1276 SX128X -TCS3414 +TCS3414 TCS3472 TLC594x TMP102 TMP12x TMP175 -TOUCH2046 +TOUCH2046 VL53L0 VL6180 WS2812 diff --git a/src/modm/driver/inertial/bmi088.hpp b/src/modm/driver/inertial/bmi088.hpp new file mode 100644 index 0000000000..10156921c4 --- /dev/null +++ b/src/modm/driver/inertial/bmi088.hpp @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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_BMI088_HPP +#define MODM_BMI088_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bmi088_transport.hpp" + +namespace modm +{ + +/// @ingroup modm_driver_bmi088 +struct bmi088 +{ + enum class AccRange : uint8_t + { + Range3g = 0x00, //< +- 3g + Range6g = 0x01, //< +- 6g (default) + Range12g = 0x02, //< +- 12g + Range24g = 0x03 //< +- 24g + }; + + enum class GyroRange : uint8_t + { + Range2000dps = 0x00, //< +- 2000 deg/s (default) + Range1000dps = 0x01, //< +- 1000 deg/s + Range500dps = 0x02, //< +- 500 deg/s + Range250dps = 0x03, //< +- 250 deg/s + Range125dps = 0x04 //< +- 125 deg/s + }; + + struct AccData + { + /// acceleration in milli-g + Vector3f getFloat() const; + + Vector3i raw; + AccRange range; + }; + + struct GyroData + { + /// angular rate in deg/s + Vector3f getFloat() const; + + Vector3i raw; + GyroRange range; + }; + + /// Accelerometer data rate and filter bandwidth config + enum class AccRate : uint8_t + { + Rate12Hz_Bw5Hz = 0x5 | 0xA0, + Rate12Hz_Bw2Hz = 0x5 | 0x90, + Rate12Hz_Bw1Hz = 0x5 | 0x80, + Rate25Hz_Bw10Hz = 0x6 | 0xA0, + Rate25Hz_Bw5Hz = 0x6 | 0x90, + Rate25Hz_Bw3Hz = 0x6 | 0x80, + Rate50Hz_Bw20Hz = 0x7 | 0xA0, + Rate50Hz_Bw9Hz = 0x7 | 0x90, + Rate50Hz_Bw5Hz = 0x7 | 0x80, + Rate100Hz_Bw40Hz = 0x8 | 0xA0, + Rate100Hz_Bw19Hz = 0x8 | 0x90, + Rate100Hz_Bw10Hz = 0x8 | 0x80, + Rate200Hz_Bw80Hz = 0x9 | 0xA0, + Rate200Hz_Bw38Hz = 0x9 | 0x90, + Rate200Hz_Bw20Hz = 0x9 | 0x80, + Rate400Hz_Bw145Hz = 0xA | 0xA0, + Rate400Hz_Bw75Hz = 0xA | 0x90, + Rate400Hz_Bw40Hz = 0xA | 0x80, + Rate800Hz_Bw230Hz = 0xB | 0xA0, + Rate800Hz_Bw140Hz = 0xB | 0x90, + Rate800Hz_Bw80Hz = 0xB | 0x80, + Rate1600Hz_Bw280Hz = 0xC | 0xA0, + Rate1600Hz_Bw234Hz = 0xC | 0x90, + Rate1600Hz_Bw145Hz = 0xC | 0x80 + }; + + enum class AccGpioConfig : uint8_t + { + ActiveHigh = Bit1, //< Set GPIO polarity + OpenDrain = Bit2, //< Configure open-drain mode, push-pull if not set + EnableOutput = Bit3, //< Activate GPIO output function + EnableInput = Bit4 //< Activate GPIO intput function + }; + MODM_FLAGS8(AccGpioConfig); + + /// Configuration flags to map accelerometer interrupt signal to GPIO + enum class AccGpioMap : uint8_t + { + Int1FifoFull = Bit0, + Int1FifoWatermark = Bit1, + Int1DataReady = Bit2, + Int2FifoFull = Bit4, + Int2FifoWatermark = Bit5, + Int2DataReady = Bit6 + }; + MODM_FLAGS8(AccGpioMap); + + enum class AccStatus : uint8_t + { + DataReady = Bit7 + }; + MODM_FLAGS8(AccStatus); + + enum class AccPowerConf : uint8_t + { + Active = 0x00, + Suspend = 0x03 + }; + + enum class AccPowerControl : uint8_t + { + Off = 0x00, + On = 0x04 + }; + + enum class AccSelfTest : uint8_t + { + Off = 0x00, + Positive = 0x0D, + Negative = 0x09 + }; + + /// Gyroscope data rate and filter bandwidth config + enum class GyroRate : uint8_t + { + Rate2000Hz_Bw532Hz = 0x00, + Rate2000Hz_Bw230Hz = 0x01, + Rate1000Hz_Bw116Hz = 0x02, + Rate400Hz_Bw47Hz = 0x03, + Rate200Hz_Bw23Hz = 0x04, + Rate100Hz_Bw12Hz = 0x05, + Rate200Hz_Bw64Hz = 0x06, + Rate100Hz_Bw32Hz = 0x07 + }; + + enum class GyroGpioConfig : uint8_t + { + Int3ActiveHigh = Bit0, + Int3OpenDrain = Bit1, + Int4ActiveHigh = Bit2, + Int4OpenDrain = Bit3 + }; + MODM_FLAGS8(GyroGpioConfig); + + enum class GyroGpioMap : uint8_t + { + Int3DataReady = Bit0, + Int3FifoInterrupt = Bit2, + Int4FifoInterrupt = Bit5, + Int4DataReady = Bit7 + }; + MODM_FLAGS8(GyroGpioMap); + + enum class GyroSelfTest : uint8_t + { + // read-only bits + RateOk = Bit4, + Fail = Bit2, + Ready = Bit1, + // write-only bit + Trigger = Bit0 + }; + MODM_FLAGS8(GyroSelfTest); + + enum class GyroStatus : uint8_t + { + DataReady = Bit7, + FifoInterrupt = Bit4 + }; + MODM_FLAGS8(GyroStatus); + + enum class GyroInterruptControl : uint8_t + { + Fifo = Bit6, + DataReady = Bit7 + }; + MODM_FLAGS8(GyroInterruptControl); +}; + +/** + * Bosch BMI088 IMU + * + * The device contains an accelerometer and a gyroscope which operate + * independently. + * + * The device supports both I2C and SPI. For applications with high data + * rates or strict timing requirements SPI is recommended. + * + * The "MM Feature Set" accelerometer functions are not supported yet. + * + * @tparam Transport Transport layer (use @ref Bmi088SpiTransport or @ref Bmi088I2cTransport) + * @ingroup modm_driver_bmi088 + */ +template +class Bmi088 : public bmi088, public Transport +{ +public: + /// @arg transportArgs Arguments to transport layer. + /// Pass addresses for I2C, none for SPI. + template + Bmi088(Args... transportArgs); + + /// Initialize device. Call before any other member function. + /// @return true on success, false on error + bool + initialize(bool runSelfTest); + + // Accelerometer functions + + std::optional + readAccData(); + + /// Read data ready flag from ACC_STATUS register. Cleared on data read. + bool + readAccDataReady(); + + /// Read and clear data ready flag in ACC_INT_STAT_1 register. Not cleared on data read. + bool + clearAccDataReadyInterrupt(); + + /// @return true on success, false on error + bool + setAccRate(AccRate config); + + /// @return true on success, false on error + bool + setAccRange(AccRange range); + + /// @return true on success, false on error + bool + setAccInt1GpioConfig(AccGpioConfig_t config); + + /// @return true on success, false on error + bool + setAccInt2GpioConfig(AccGpioConfig_t config); + + /// @return true on success, false on error + bool + setAccGpioMap(AccGpioMap_t map); + + // Gyroscope functions + + std::optional + readGyroData(); + + /** + * Read Data ready interrupt status. + * @warning The interrupt is cleared automatically after 280-400 μs. + * @return true on success, false on error + */ + bool + readGyroDataReady(); + + /// @return true on success, false on error + bool + setGyroRate(GyroRate config); + + /// @return true on success, false on error + bool + setGyroRange(GyroRange range); + + /// @return true on success, false on error + bool + setGyroGpioConfig(GyroGpioConfig_t config); + + /// @return true on success, false on error + bool + setGyroGpioMap(GyroGpioMap_t map); + +private: + using AccRegister = Transport::AccRegister; + using GyroRegister = Transport::GyroRegister; + + static constexpr std::chrono::milliseconds ResetTimeout{30}; + static constexpr std::chrono::microseconds WriteTimeout{2}; + static constexpr std::chrono::microseconds AccSuspendTimeout{450}; + + static constexpr uint8_t ResetCommand{0xB6}; + static constexpr uint8_t AccChipId{0x1E}; + static constexpr uint8_t GyroChipId{0x0F}; + + bool + checkChipId(); + + bool + reset(); + + bool + selfTest(); + + bool + enableAccelerometer(); + + void + timerWait(); + + std::optional + readRegister(auto reg); + + modm::PreciseTimeout timer_; + AccRange accRange_{AccRange::Range6g}; + GyroRange gyroRange_{GyroRange::Range2000dps}; +}; + + +} // modm namespace + +#include "bmi088_impl.hpp" + +#endif // MODM_ADIS16470_HPP diff --git a/src/modm/driver/inertial/bmi088.lb b/src/modm/driver/inertial/bmi088.lb new file mode 100644 index 0000000000..1d16099368 --- /dev/null +++ b/src/modm/driver/inertial/bmi088.lb @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Christopher Durand +# +# 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 = ":driver:bmi088" + module.description = """\ +# BMI088 Inertial Measurement Unit + +[Datasheet](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi088-ds001.pdf) +""" + +def prepare(module, options): + module.depends( + ":architecture:gpio", + ":architecture:register", + ":architecture:spi.device", + ":architecture:i2c.device", + ":math:geometry", + ":processing:fiber", + ":processing:timer") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/inertial" + env.copy("bmi088.hpp") + env.copy("bmi088_impl.hpp") + env.copy("bmi088_transport.hpp") + env.copy("bmi088_transport_impl.hpp") diff --git a/src/modm/driver/inertial/bmi088_impl.hpp b/src/modm/driver/inertial/bmi088_impl.hpp new file mode 100644 index 0000000000..b94dac0604 --- /dev/null +++ b/src/modm/driver/inertial/bmi088_impl.hpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 +#ifndef MODM_BMI088_HPP +#error "Don't include this file directly, use 'bmi088.hpp' instead!" +#endif + +#include + +namespace modm +{ + +template +template +Bmi088::Bmi088(Args... transportArgs) + : Transport{transportArgs...} +{ +} + +template +bool +Bmi088::initialize(bool runSelfTest) +{ + Transport::initialize(); + + if (!checkChipId() or !reset() or !enableAccelerometer()) { + return false; + } + + if (runSelfTest and !selfTest()) { + return false; + } + + timerWait(); + const bool ok = this->writeRegister(GyroRegister::InterruptControl, + uint8_t(GyroInterruptControl::DataReady)); + timer_.restart(WriteTimeout); + return ok; +} + +template +std::optional +Bmi088::readAccData() +{ + const auto data = this->readRegisters(AccRegister::DataXLow, 6); + + if (data.empty()) { + return {}; + } + + return AccData { + .raw = Vector3i( + data[0] | data[1] << 8, + data[2] | data[3] << 8, + data[4] | data[5] << 8 + ), + .range = accRange_ + }; +} + +template +bool +Bmi088::readAccDataReady() +{ + const auto value = readRegister(AccRegister::Status).value_or(0); + return value & uint8_t(AccStatus::DataReady); +} + +template +bool +Bmi088::clearAccDataReadyInterrupt() +{ + const auto result = readRegister(AccRegister::InterruptStatus).value_or(0); + return result & uint8_t(AccStatus::DataReady); +} + +template +bool +Bmi088::setAccRate(AccRate config) +{ + timerWait(); + const bool ok = this->writeRegister(AccRegister::Config, uint8_t(config)); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::setAccRange(AccRange range) +{ + timerWait(); + const bool ok = this->writeRegister(AccRegister::Range, uint8_t(range)); + timer_.restart(ResetTimeout); + if (ok) { + accRange_ = range; + return true; + } + return false; +} + +template +bool +Bmi088::setAccInt1GpioConfig(AccGpioConfig_t config) +{ + timerWait(); + const bool ok = this->writeRegister(AccRegister::Int1Control, config.value); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::setAccInt2GpioConfig(AccGpioConfig_t config) +{ + timerWait(); + const bool ok = this->writeRegister(AccRegister::Int2Control, config.value); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::setAccGpioMap(AccGpioMap_t map) +{ + timerWait(); + const bool ok = this->writeRegister(AccRegister::IntMap, map.value); + timer_.restart(ResetTimeout); + return ok; +} + +template +std::optional +Bmi088::readGyroData() +{ + const auto data = this->readRegisters(GyroRegister::RateXLow, 6); + + if (data.empty()) { + return {}; + } + + return GyroData { + .raw = Vector3i( + data[0] | data[1] << 8, + data[2] | data[3] << 8, + data[4] | data[5] << 8 + ), + .range = gyroRange_ + }; +} + +template +bool +Bmi088::readGyroDataReady() +{ + const auto value = readRegister(GyroRegister::InterruptStatus).value_or(0); + return bool(GyroStatus_t{value} & GyroStatus::DataReady); +} + +template +bool +Bmi088::setGyroRate(GyroRate rate) +{ + timerWait(); + const bool ok = this->writeRegister(GyroRegister::Bandwidth, uint8_t(rate)); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::setGyroRange(GyroRange range) +{ + timerWait(); + const bool ok = this->writeRegister(GyroRegister::Range, uint8_t(range)); + timer_.restart(ResetTimeout); + if (ok) { + gyroRange_ = range; + return true; + } + return false; +} + +template +bool +Bmi088::setGyroGpioConfig(GyroGpioConfig_t config) +{ + timerWait(); + const bool ok = this->writeRegister(GyroRegister::Int3Int4Conf, config.value); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::setGyroGpioMap(GyroGpioMap_t map) +{ + timerWait(); + const bool ok = this->writeRegister(GyroRegister::Int3Int4Map, map.value); + timer_.restart(ResetTimeout); + return ok; +} + +template +bool +Bmi088::checkChipId() +{ + const std::optional gyroId = readRegister(GyroRegister::ChipId); + const std::optional accId = readRegister(AccRegister::ChipId); + const bool gyroIdValid = gyroId.value_or(0) == GyroChipId; + const bool accIdValid = accId.value_or(0) == AccChipId; + return gyroIdValid and accIdValid; +} + +template +void +Bmi088::timerWait() +{ + while (timer_.isArmed()) { + modm::fiber::yield(); + } +} + +template +std::optional +Bmi088::readRegister(auto reg) +{ + const auto data = this->readRegisters(reg, 1); + if (data.empty()) { + return std::nullopt; + } else { + return data[0]; + } +} + +template +bool +Bmi088::reset() +{ + const bool gyroOk = this->writeRegister(GyroRegister::SoftReset, ResetCommand); + const bool accOk = this->writeRegister(AccRegister::SoftReset, ResetCommand); + + if (!gyroOk or !accOk) { + return false; + } + accRange_ = AccRange::Range6g; + gyroRange_ = GyroRange::Range2000dps; + + timer_.restart(ResetTimeout); + timerWait(); + + // required to switch the accelerometer to SPI mode if SPI is used + Transport::initialize(); + + return true; +} + +template +bool +Bmi088::selfTest() +{ + bool ok = setAccRange(AccRange::Range24g); + ok &= setAccRate(AccRate::Rate1600Hz_Bw280Hz); + if (!ok) { + return false; + } + timer_.restart(2ms); + timerWait(); + ok = this->writeRegister(AccRegister::SelfTest, uint8_t(AccSelfTest::Positive)); + if (!ok) { + return false; + } + timer_.restart(50ms); + timerWait(); + const auto positiveData = readAccData(); + if (!positiveData) { + return false; + } + ok = this->writeRegister(AccRegister::SelfTest, uint8_t(AccSelfTest::Negative)); + if (!ok) { + return false; + } + timer_.restart(50ms); + timerWait(); + const auto negativeData = readAccData(); + if (!negativeData) { + return false; + } + const Vector3f diff = positiveData->getFloat() - negativeData->getFloat(); + const bool accOk = (diff[0] >= 1000.f) and (diff[1] >= 1000.f) and (diff[2] >= 500.f); + this->writeRegister(AccRegister::SelfTest, uint8_t(AccSelfTest::Off)); + if (!accOk) { + return false; + } + ok = this->writeRegister(GyroRegister::SelfTest, uint8_t(GyroSelfTest::Trigger)); + if (!ok) { + return false; + } + timer_.restart(30ms); + timerWait(); + const auto gyroTest = GyroSelfTest_t(readRegister(GyroRegister::SelfTest).value_or(0)); + if (gyroTest != (GyroSelfTest::Ready | GyroSelfTest::RateOk)) { + return false; + } + return reset() and enableAccelerometer(); +} + +template +bool +Bmi088::enableAccelerometer() +{ + timerWait(); + bool ok = this->writeRegister(AccRegister::PowerConfig, uint8_t(AccPowerConf::Active)); + timer_.restart(AccSuspendTimeout); + if (!ok) { + return false; + } + timerWait(); + + ok = this->writeRegister(AccRegister::PowerControl, uint8_t(AccPowerControl::On)); + timer_.restart(WriteTimeout); + return ok; +} + +inline Vector3f +bmi088::AccData::getFloat() const +{ + const float factor = (1500 << (int(range) + 1)) * (1 / 32768.f); + return Vector3f { + raw[0] * factor, + raw[1] * factor, + raw[2] * factor + }; +} + +inline Vector3f +bmi088::GyroData::getFloat() const +{ + const float factor = (2000 >> int(range)) * (1 / 32768.f); + return Vector3f { + raw[0] * factor, + raw[1] * factor, + raw[2] * factor + }; +} + +} // namespace modm diff --git a/src/modm/driver/inertial/bmi088_transport.hpp b/src/modm/driver/inertial/bmi088_transport.hpp new file mode 100644 index 0000000000..569bdd71dc --- /dev/null +++ b/src/modm/driver/inertial/bmi088_transport.hpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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_BMI088_TRANSPORT_HPP +#define MODM_BMI088_TRANSPORT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_bmi088 +struct Bmi088TransportBase +{ + enum class + AccRegister : uint8_t + { + ChipId = 0x00, + // 0x01: reserved + Error = 0x02, + Status = 0x03, + // 0x04-0x11: reserved + DataXLow = 0x12, + DataXHigh = 0x13, + DataYLow = 0x14, + DataYHigh = 0x15, + DataZLow = 0x16, + DataZHigh = 0x17, + SensorTime0 = 0x18, + SensorTime1 = 0x19, + SensorTime2 = 0x1A, + // 0x1B-0x1C: reserved + InterruptStatus = 0x1D, + // 0x1E-0x21: reserved + TempHigh = 0x22, + TempLow = 0x23, + FifoLength0 = 0x24, + FifoLength1 = 0x25, + FifoData = 0x26, + // 0x27-0x3F: reserved + Config = 0x40, + Range = 0x41, + // 0x42-0x44: reserved + FifoDownsampling = 0x45, + FifoWatermark0 = 0x46, + FifoWatermark1 = 0x47, + FifoConfig0 = 0x48, + FifoConfig1 = 0x49, + // 0x4A-0x52: reserved + Int1Control = 0x53, + Int2Control = 0x54, + // 0x55-0x57: reserved + IntMap = 0x58, + // 0x59-0x6C: reserved + SelfTest = 0x6D, + // 0x6E-0x7B: reserved + PowerConfig = 0x7C, + PowerControl = 0x7D, + SoftReset = 0x7E + }; + + enum class + GyroRegister : uint8_t + { + ChipId = 0x00, + // 0x01: reserved + RateXLow = 0x02, + RateXHigh = 0x03, + RateYLow = 0x04, + RateYHigh = 0x05, + RateZLow = 0x06, + RateZHigh = 0x07, + // 0x08-0x09: reserved + InterruptStatus = 0x0A, + // 0x0B-0x0D: reserved + FifoStatus = 0x0E, + Range = 0x0F, + Bandwidth = 0x10, + LowPowerMode1 = 0x11, + // 0x12-0x13: reserved + SoftReset = 0x14, + InterruptControl = 0x15, + Int3Int4Conf = 0x16, + // 0x17: reserved + Int3Int4Map = 0x18, + // 0x19-0x1D: reserved + FifoWatermark = 0x1E, + // 0x1F-0x33: reserved + FifoExtInt = 0x34, + // 0x35-0x3B: reserved + SelfTest = 0x3C, + FifoConfig0 = 0x3D, + FifoConfig1 = 0x3E, + FifoData = 0x3F + }; + static constexpr uint8_t MaxRegisterSequence{6}; +}; + +/// @ingroup modm_driver_bmi088 +template +concept Bmi088Transport = requires(T& transport, Bmi088TransportBase::AccRegister reg1, + Bmi088TransportBase::GyroRegister reg2, uint8_t data) +{ + { transport.initialize() }; + { transport.readRegisters(reg1, /* count= */data) } -> std::same_as>; + { transport.readRegisters(reg2, /* count= */data) } -> std::same_as>; + { transport.writeRegister(reg1, data) } -> std::same_as; + { transport.writeRegister(reg2, data) } -> std::same_as; +}; + +/** + * BMI088 SPI transport. Pass as template parameter to Bmi088 driver class to + * use the driver with SPI. + * + * The transport class contains internal buffers which allow to read all three + * gyroscope or accelerometer axes in a single DMA transaction. + * + * @tparam SpiMaster SPI master the device is connected to + * @tparam AccCs accelerometer chip-select GPIO + * @tparam GyroCs gyroscope chip-select GPIO + * @ingroup modm_driver_bmi088 + */ +template +class Bmi088SpiTransport : public Bmi088TransportBase, + public SpiDevice +{ +public: + Bmi088SpiTransport() = default; + + Bmi088SpiTransport(const Bmi088SpiTransport&) = delete; + + Bmi088SpiTransport& + operator=(const Bmi088SpiTransport&) = delete; + + void + initialize(); + + std::span + readRegisters(AccRegister startReg, uint8_t count); + + std::span + readRegisters(GyroRegister startReg, uint8_t count); + + bool + writeRegister(AccRegister reg, uint8_t data); + + bool + writeRegister(GyroRegister reg, uint8_t data); + +private: + template + std::span + readRegisters(uint8_t reg, uint8_t count, bool dummyByte); + + template + bool + writeRegister(uint8_t reg, uint8_t data); + + static constexpr uint8_t ReadFlag{0b1000'0000}; + + std::array rxBuffer_{}; + std::array txBuffer_{}; +}; + + +/** + * BMI088 I2C transport. Pass as template parameter to Bmi088 driver class to + * use the driver with I2C. + * + * @tparam I2cMaster I2c master the device is connected to + * @ingroup modm_driver_bmi088 + */ +template +class Bmi088I2cTransport : public Bmi088TransportBase, public I2cDevice +{ +public: + Bmi088I2cTransport(uint8_t accAddress, uint8_t gyroAddress); + + Bmi088I2cTransport(const Bmi088I2cTransport&) = delete; + + Bmi088I2cTransport& + operator=(const Bmi088I2cTransport&) = delete; + + void + initialize(); + + std::span + readRegisters(AccRegister startReg, uint8_t count); + + std::span + readRegisters(GyroRegister startReg, uint8_t count); + + bool + writeRegister(AccRegister reg, uint8_t data); + + bool + writeRegister(GyroRegister reg, uint8_t data); + +private: + std::span + readRegisters(uint8_t reg, uint8_t count); + + bool + writeRegister(uint8_t reg, uint8_t data); + + uint8_t accAddress_; + uint8_t gyroAddress_; + std::array buffer_{}; +}; + +} // modm namespace + +#include "bmi088_transport_impl.hpp" + +#endif // MODM_BMI088_TRANSPORT_HPP diff --git a/src/modm/driver/inertial/bmi088_transport_impl.hpp b/src/modm/driver/inertial/bmi088_transport_impl.hpp new file mode 100644 index 0000000000..8e62894091 --- /dev/null +++ b/src/modm/driver/inertial/bmi088_transport_impl.hpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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_BMI088_TRANSPORT_HPP +#error "Don't include this file directly, use 'bmi088_transport.hpp' instead!" +#endif + +#include + +namespace modm +{ + +template +void +Bmi088SpiTransport::initialize() +{ + // rising edge on CS required to enable accelerometer SPI mode + AccCs::setOutput(false); + modm::delay_ns(100); + AccCs::setOutput(true); + + GyroCs::setOutput(true); +} + +template +std::span +Bmi088SpiTransport::readRegisters(AccRegister startReg, + uint8_t count) +{ + return readRegisters(static_cast(startReg), count, true); +} + +template +std::span +Bmi088SpiTransport::readRegisters(GyroRegister startReg, + uint8_t count) +{ + return readRegisters(static_cast(startReg), count, false); +} + +template +template +std::span +Bmi088SpiTransport::readRegisters(uint8_t startReg, + uint8_t count, bool dummyByte) +{ + if (count > MaxRegisterSequence) { + return {}; + } + + while (!this->acquireMaster()) { + modm::fiber::yield(); + } + Cs::reset(); + + const uint8_t dataOffset = (dummyByte ? 2 : 1); + + txBuffer_[0] = startReg | ReadFlag; + txBuffer_[1] = 0; + + SpiMaster::transfer(&txBuffer_[0], &rxBuffer_[0], count + dataOffset); + + if (this->releaseMaster()) { + Cs::set(); + } + + return std::span{&rxBuffer_[dataOffset], count}; +} + +template +bool +Bmi088SpiTransport::writeRegister(AccRegister reg, uint8_t data) +{ + return writeRegister(static_cast(reg), data); +} + +template +bool +Bmi088SpiTransport::writeRegister(GyroRegister reg, uint8_t data) +{ + return writeRegister(static_cast(reg), data); +} + +template +template +bool +Bmi088SpiTransport::writeRegister(uint8_t reg, uint8_t data) +{ + while (!this->acquireMaster()) { + modm::fiber::yield(); + } + Cs::reset(); + + txBuffer_[0] = reg; + txBuffer_[1] = data; + SpiMaster::transfer(&txBuffer_[0], nullptr, 2); + + if (this->releaseMaster()) { + Cs::set(); + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ + +template +Bmi088I2cTransport::Bmi088I2cTransport(uint8_t accAddress, uint8_t gyroAddress) + : I2cDevice{accAddress}, accAddress_{accAddress}, gyroAddress_{gyroAddress} +{ +} + +template +void +Bmi088I2cTransport::initialize() +{ +} + +template +std::span +Bmi088I2cTransport::readRegisters(AccRegister startReg, uint8_t count) +{ + this->transaction.setAddress(accAddress_); + return readRegisters(static_cast(startReg), count); +} + +template +std::span +Bmi088I2cTransport::readRegisters(GyroRegister startReg, uint8_t count) +{ + this->transaction.setAddress(gyroAddress_); + return readRegisters(static_cast(startReg), count); +} + +template +std::span +Bmi088I2cTransport::readRegisters(uint8_t startReg, uint8_t count) +{ + if (count > MaxRegisterSequence) { + return {}; + } + + this->transaction.configureWriteRead(&startReg, 1, &buffer_[0], count); + const bool success = this->runTransaction(); + + if (success) { + return std::span{&buffer_[0], count}; + } else { + return {}; + } +} + +template +bool +Bmi088I2cTransport::writeRegister(AccRegister reg, uint8_t data) +{ + this->transaction.setAddress(accAddress_); + return writeRegister(static_cast(reg), data); +} + +template +bool +Bmi088I2cTransport::writeRegister(GyroRegister reg, uint8_t data) +{ + this->transaction.setAddress(gyroAddress_); + return writeRegister(static_cast(reg), data); +} + +template +bool +Bmi088I2cTransport::writeRegister(uint8_t reg, uint8_t data) +{ + buffer_[0] = reg; + buffer_[1] = data; + this->transaction.configureWrite(&buffer_[0], 2); + + return this->runTransaction(); +} + +} // namespace modm From 06c07006827e8e065256b76bc10394c56b92461f Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 20 Jul 2023 16:57:18 +0200 Subject: [PATCH 070/159] [example] Add BMI088 SPI example for Nucleo H723ZG --- examples/nucleo_h723zg/bmi088/spi/main.cpp | 103 ++++++++++++++++++ examples/nucleo_h723zg/bmi088/spi/project.xml | 15 +++ 2 files changed, 118 insertions(+) create mode 100644 examples/nucleo_h723zg/bmi088/spi/main.cpp create mode 100644 examples/nucleo_h723zg/bmi088/spi/project.xml diff --git a/examples/nucleo_h723zg/bmi088/spi/main.cpp b/examples/nucleo_h723zg/bmi088/spi/main.cpp new file mode 100644 index 0000000000..9704a8c852 --- /dev/null +++ b/examples/nucleo_h723zg/bmi088/spi/main.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 + +using namespace Board; + +using Spi = SpiMaster2_Dma; +using CsGyro = GpioC0; +using CsAcc = GpioD6; +using Mosi = GpioC3; +using Miso = GpioC2; +using Sck = GpioD3; + +using AccInt1 = GpioC8; +using GyroInt3 = GpioC9; + +using Transport = modm::Bmi088SpiTransport; +using Imu = modm::Bmi088; +Imu imu; + +void initializeImu() +{ + AccInt1::setInput(AccInt1::InputType::PullDown); + GyroInt3::setInput(GyroInt3::InputType::PullDown); + + constexpr bool selfTest = true; + while (!imu.initialize(selfTest)) { + MODM_LOG_ERROR << "Initialization failed, retrying ...\n"; + modm::delay(500ms); + } + + bool ok = imu.setAccRate(Imu::AccRate::Rate12Hz_Bw5Hz); + ok &= imu.setAccRange(Imu::AccRange::Range3g); + + const auto int1Config = (Imu::AccGpioConfig::ActiveHigh | Imu::AccGpioConfig::EnableOutput); + ok &= imu.setAccInt1GpioConfig(int1Config); + ok &= imu.setAccGpioMap(Imu::AccGpioMap::Int1DataReady); + + ok &= imu.setGyroRate(Imu::GyroRate::Rate100Hz_Bw12Hz); + ok &= imu.setGyroRange(Imu::GyroRange::Range250dps); + ok &= imu.setGyroGpioConfig(Imu::GyroGpioConfig::Int3ActiveHigh); + ok &= imu.setGyroGpioMap(Imu::GyroGpioMap::Int3DataReady); + + if (!ok) { + MODM_LOG_ERROR << "Configuration failed!\n"; + } +} + +int main() +{ + Board::initialize(); + Leds::setOutput(); + Dma1::enable(); + Spi::connect(); + Spi::initialize(); + + MODM_LOG_INFO << "BMI088 SPI Test\n"; + initializeImu(); + + std::atomic_bool accReady = false; + std::atomic_bool gyroReady = false; + + Exti::connect(Exti::Trigger::RisingEdge, [&accReady](auto){ + accReady = true; + }); + + Exti::connect(Exti::Trigger::RisingEdge, [&gyroReady](auto){ + gyroReady = true; + }); + + while (true) + { + while(!accReady or !gyroReady); + + const std::optional accResult = imu.readAccData(); + accReady = false; + const std::optional gyroResult = imu.readGyroData(); + gyroReady = false; + + if (accResult) { + const modm::Vector3f data = accResult->getFloat(); + MODM_LOG_INFO.printf("Acc [mg]\tx:\t%5.1f\ty: %5.1f\tz: %5.1f\n", data[0], data[1], data[2]); + } + if (gyroResult) { + const modm::Vector3f data = gyroResult->getFloat(); + MODM_LOG_INFO.printf("Gyro [deg/s]\tx:\t%5.2f\ty: %5.2f\tz: %5.2f\n", data[0], data[1], data[2]); + } + } + + return 0; +} diff --git a/examples/nucleo_h723zg/bmi088/spi/project.xml b/examples/nucleo_h723zg/bmi088/spi/project.xml new file mode 100644 index 0000000000..0ee7b8a80b --- /dev/null +++ b/examples/nucleo_h723zg/bmi088/spi/project.xml @@ -0,0 +1,15 @@ + + modm:nucleo-h723zg + + + + + + modm:build:scons + modm:processing:fiber + modm:platform:dma + modm:platform:exti + modm:platform:spi:2 + modm:driver:bmi088 + + From a771042eb5d503acdae5ac18d08c61932b7c3618 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 4 Oct 2023 22:44:50 +0200 Subject: [PATCH 071/159] [example] Add BMI088 I2C example for Nucleo H723ZG --- examples/nucleo_h723zg/bmi088/i2c/main.cpp | 102 ++++++++++++++++++ examples/nucleo_h723zg/bmi088/i2c/project.xml | 14 +++ 2 files changed, 116 insertions(+) create mode 100644 examples/nucleo_h723zg/bmi088/i2c/main.cpp create mode 100644 examples/nucleo_h723zg/bmi088/i2c/project.xml diff --git a/examples/nucleo_h723zg/bmi088/i2c/main.cpp b/examples/nucleo_h723zg/bmi088/i2c/main.cpp new file mode 100644 index 0000000000..17b6847d17 --- /dev/null +++ b/examples/nucleo_h723zg/bmi088/i2c/main.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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 + +using namespace Board; + +using I2c = I2cMaster1; +using Scl = GpioB8; // D15 +using Sda = GpioB9; // D14 + +using AccInt1 = GpioC6; +using GyroInt3 = GpioB15; + +using Transport = modm::Bmi088I2cTransport; +using Imu = modm::Bmi088; + +constexpr uint8_t AccAddress = 0x18; +constexpr uint8_t GyroAddress = 0x68; +Imu imu{AccAddress, GyroAddress}; + +void initializeImu() +{ + AccInt1::setInput(AccInt1::InputType::PullDown); + GyroInt3::setInput(GyroInt3::InputType::PullDown); + + constexpr bool selfTest = true; + while (!imu.initialize(selfTest)) { + MODM_LOG_ERROR << "Initialization failed, retrying ...\n"; + modm::delay(500ms); + } + + bool ok = imu.setAccRate(Imu::AccRate::Rate12Hz_Bw5Hz); + ok &= imu.setAccRange(Imu::AccRange::Range3g); + + const auto int1Config = (Imu::AccGpioConfig::ActiveHigh | Imu::AccGpioConfig::EnableOutput); + ok &= imu.setAccInt1GpioConfig(int1Config); + ok &= imu.setAccGpioMap(Imu::AccGpioMap::Int1DataReady); + + ok &= imu.setGyroRate(Imu::GyroRate::Rate100Hz_Bw12Hz); + ok &= imu.setGyroRange(Imu::GyroRange::Range250dps); + ok &= imu.setGyroGpioConfig(Imu::GyroGpioConfig::Int3ActiveHigh); + ok &= imu.setGyroGpioMap(Imu::GyroGpioMap::Int3DataReady); + + if (!ok) { + MODM_LOG_ERROR << "Configuration failed!\n"; + } +} + +int main() +{ + Board::initialize(); + Leds::setOutput(); + I2c::connect(I2c::PullUps::Internal); + I2c::initialize(); + + MODM_LOG_INFO << "BMI088 I2C Test\n"; + initializeImu(); + + std::atomic_bool accReady = false; + std::atomic_bool gyroReady = false; + + Exti::connect(Exti::Trigger::RisingEdge, [&accReady](auto){ + accReady = true; + }); + + Exti::connect(Exti::Trigger::RisingEdge, [&gyroReady](auto){ + gyroReady = true; + }); + + while (true) + { + while(!accReady or !gyroReady); + + const std::optional accResult = imu.readAccData(); + accReady = false; + const std::optional gyroResult = imu.readGyroData(); + gyroReady = false; + + if (accResult) { + const modm::Vector3f data = accResult->getFloat(); + MODM_LOG_INFO.printf("Acc [mg]\tx:\t%5.1f\ty: %5.1f\tz: %5.1f\n", data[0], data[1], data[2]); + } + if (gyroResult) { + const modm::Vector3f data = gyroResult->getFloat(); + MODM_LOG_INFO.printf("Gyro [deg/s]\tx:\t%5.2f\ty: %5.2f\tz: %5.2f\n", data[0], data[1], data[2]); + } + } + + return 0; +} diff --git a/examples/nucleo_h723zg/bmi088/i2c/project.xml b/examples/nucleo_h723zg/bmi088/i2c/project.xml new file mode 100644 index 0000000000..797c04a836 --- /dev/null +++ b/examples/nucleo_h723zg/bmi088/i2c/project.xml @@ -0,0 +1,14 @@ + + modm:nucleo-h723zg + + + + + + modm:build:scons + modm:processing:fiber + modm:platform:exti + modm:platform:i2c:1 + modm:driver:bmi088 + + From 354da57dc0c7bf9ad2135905bb81a8d5c1e0b289 Mon Sep 17 00:00:00 2001 From: Sergey Pluzhnikov Date: Fri, 3 Nov 2023 16:16:54 +0100 Subject: [PATCH 072/159] Add isCountingDown/Up functions to STM32 Timers --- src/modm/platform/timer/stm32/advanced.hpp.in | 12 ++++++++++ src/modm/platform/timer/stm32/basic.hpp.in | 12 ++++++++++ .../platform/timer/stm32/basic_base.hpp.in | 8 +++++++ .../timer/stm32/general_purpose.hpp.in | 24 +++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/src/modm/platform/timer/stm32/advanced.hpp.in b/src/modm/platform/timer/stm32/advanced.hpp.in index e9d26dc31f..3d2ca0d6d3 100644 --- a/src/modm/platform/timer/stm32/advanced.hpp.in +++ b/src/modm/platform/timer/stm32/advanced.hpp.in @@ -231,6 +231,18 @@ public: return true; } + static inline bool + isCountingUp() + { + return (TIM{{ id }}->CR1 & TIM_CR1_DIR) == 0; + } + + static inline bool + isCountingDown() + { + return !isCountingUp(); + } + static inline void enableOutput() { diff --git a/src/modm/platform/timer/stm32/basic.hpp.in b/src/modm/platform/timer/stm32/basic.hpp.in index e7c246fb42..b8902e0a50 100644 --- a/src/modm/platform/timer/stm32/basic.hpp.in +++ b/src/modm/platform/timer/stm32/basic.hpp.in @@ -166,6 +166,18 @@ public: TIM{{ id }}->CNT = value; } + static inline bool + isCountingUp() + { + return true; + } + + static inline bool + isCountingDown() + { + return false; + } + static constexpr bool hasAdvancedPwmControl() { diff --git a/src/modm/platform/timer/stm32/basic_base.hpp.in b/src/modm/platform/timer/stm32/basic_base.hpp.in index 864e53e4c2..40ab3a3754 100644 --- a/src/modm/platform/timer/stm32/basic_base.hpp.in +++ b/src/modm/platform/timer/stm32/basic_base.hpp.in @@ -216,6 +216,14 @@ public: static constexpr bool hasAdvancedPwmControl(); + /** + * Check current count direction + */ + static inline bool + isCountingUp(); + static inline bool + isCountingDown(); + /** * Enables or disables the Interrupt Vector. * diff --git a/src/modm/platform/timer/stm32/general_purpose.hpp.in b/src/modm/platform/timer/stm32/general_purpose.hpp.in index cf79e2142d..d4826f44f2 100644 --- a/src/modm/platform/timer/stm32/general_purpose.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.hpp.in @@ -259,6 +259,18 @@ public: %% if target.family not in ["l0", "l1"] and id in [15, 16, 17] + static inline bool + isCountingUp() + { + return true; + } + + static inline bool + isCountingDown() + { + return false; + } + static constexpr bool hasAdvancedPwmControl() { @@ -356,6 +368,18 @@ public: { return false; } + + static inline bool + isCountingUp() + { + return (TIM{{ id }}->CR1 & TIM_CR1_DIR) == 0; + } + + static inline bool + isCountingDown() + { + return !isCountingUp(); + } %% endif From 605aaba1127ea7fe6e6e1a84042849732fd55cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20B=C3=B6ckmann?= Date: Sun, 5 Nov 2023 19:19:31 +0100 Subject: [PATCH 073/159] Update example main in custom-project documentation. --- docs/src/guide/custom-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guide/custom-project.md b/docs/src/guide/custom-project.md index d1096008ec..aed899c316 100644 --- a/docs/src/guide/custom-project.md +++ b/docs/src/guide/custom-project.md @@ -144,7 +144,7 @@ int main() while (true) { Board::Leds::toggle(); - modm::delayMilliseconds(Board::Button::read() ? 250 : 500); + modm::delay(Board::Button::read() ? 250ms : 500ms); #ifdef MODM_BOARD_HAS_LOGGER static uint32_t counter(0); MODM_LOG_INFO << "Loop counter: " << (counter++) << modm::endl; From efb40cc2d57353173a3bd5b8a2a4192c4f4beb13 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 16 Nov 2023 23:28:16 +0100 Subject: [PATCH 074/159] [stm32] Place stack in SRAM by default on H7, add lbuild option The DTCM is not DMA-capable on H7 devices. An lbuild option is added to select the stack location between SRAM and DTCM. --- src/modm/platform/core/stm32/idtcm.ld.in | 4 ++++ src/modm/platform/core/stm32/module.lb | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/modm/platform/core/stm32/idtcm.ld.in b/src/modm/platform/core/stm32/idtcm.ld.in index 8a8a9152c0..b133457e05 100644 --- a/src/modm/platform/core/stm32/idtcm.ld.in +++ b/src/modm/platform/core/stm32/idtcm.ld.in @@ -31,7 +31,11 @@ SECTIONS %% set dtcm_section = "DTCM" %% endif +%% if stack_in_dtcm {{ linker.section_stack(dtcm_section) }} +%% else +{{ linker.section_stack(cont_ram.cont_name|upper) }} +%% endif %% if "dtcm" in cont_ram.cont_name {{ linker.section_ram(cont_ram.cont_name|upper, "FLASH", table_copy, table_zero, diff --git a/src/modm/platform/core/stm32/module.lb b/src/modm/platform/core/stm32/module.lb index 93b376ffa6..8e0141b5c4 100644 --- a/src/modm/platform/core/stm32/module.lb +++ b/src/modm/platform/core/stm32/module.lb @@ -30,6 +30,15 @@ def prepare(module, options): default="rom") ) + if options[":target"].identifier.family == "h7": + module.add_option( + EnumerationOption( + name="main_stack_location", + description="SRAM (default) or DTCM (faster, but not DMA-capable)", + enumeration=["sram", "dtcm"], + default="sram") + ) + module.depends(":platform:cortex-m") return True @@ -100,5 +109,6 @@ def post_build(env): linkerscript = "dccm.ld.in" elif memory["name"] == "dtcm": # Executable ITCM and DTCM (Tightly-Coupled Memory) + env.substitutions["stack_in_dtcm"] = env.get(":platform:core:main_stack_location", "dtcm") == "dtcm" linkerscript = "idtcm.ld.in" env.template(linkerscript, "linkerscript.ld") From 825e53e1b14d85d9d170153a2d9eb6232b6e0b6a Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 18 Nov 2023 23:28:38 +0100 Subject: [PATCH 075/159] [scons] Use the same Python for XPCC builders --- .../scons/site_tools/xpcc_generator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/build_script_generator/scons/site_tools/xpcc_generator.py b/tools/build_script_generator/scons/site_tools/xpcc_generator.py index eed9fa8c45..c7a933476c 100644 --- a/tools/build_script_generator/scons/site_tools/xpcc_generator.py +++ b/tools/build_script_generator/scons/site_tools/xpcc_generator.py @@ -135,13 +135,14 @@ def xpcc_communication_header(env, xmlfile, container, path=None, dtdPath=None, # ----------------------------------------------------------------------------- def generate(env, **kw): env.SetDefault(XPCC_SYSTEM_DESIGN_SCANNERS = {}) + env.SetDefault(PYTHON3 = sys.executable) env['XPCC_SYSTEM_DESIGN_SCANNERS']['XML'] = SCons.Script.Scanner( function = xml_include_scanner, skeys = ['.xml']) env['BUILDERS']['SystemCppPackets'] = \ SCons.Script.Builder( action = SCons.Action.Action( - 'python3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_packets.py" ' \ + '$PYTHON3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_packets.py" ' \ '--source_path ${TARGETS[0].dir} ' \ '--header_path ${TARGETS[1].dir} ' \ '--dtdpath "${dtdPath}" ' \ @@ -158,7 +159,7 @@ def generate(env, **kw): env['BUILDERS']['SystemCppIdentifier'] = \ SCons.Script.Builder( action = SCons.Action.Action( - 'python3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_identifier.py" ' \ + '$PYTHON3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_identifier.py" ' \ '--outpath ${TARGET.dir} ' \ '--dtdpath "${dtdPath}" ' \ '--namespace "${namespace}" ' \ @@ -174,7 +175,7 @@ def generate(env, **kw): env['BUILDERS']['SystemCppPostman'] = \ SCons.Script.Builder( action = SCons.Action.Action( - 'python3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_postman.py" ' \ + '$PYTHON3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_postman.py" ' \ '--container "${container}" ' \ '--outpath ${TARGET.dir} ' \ '--dtdpath "${dtdPath}" ' \ @@ -191,7 +192,7 @@ def generate(env, **kw): env['BUILDERS']['SystemCppCommunication'] = \ SCons.Script.Builder( action = SCons.Action.Action( - 'python3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_communication.py" ' \ + '$PYTHON3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_communication.py" ' \ '--outpath ${TARGET.dir} ' \ '--dtdpath "${dtdPath}" ' \ '--namespace "${namespace}" ' \ @@ -207,7 +208,7 @@ def generate(env, **kw): env['BUILDERS']['SystemCppXpccTaskCaller'] = \ SCons.Script.Builder( action = SCons.Action.Action( - 'python3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_xpcc_task_caller.py" ' \ + '$PYTHON3 "${XPCC_SYSTEM_DESIGN}/builder/cpp_xpcc_task_caller.py" ' \ '--outpath ${TARGET.dir} ' \ '--dtdpath "${dtdPath}" ' \ '--namespace "${namespace}" ' \ From ca8017074ebdfde90ff4d10eccd43b61367cee90 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 28 Dec 2023 02:02:36 +0100 Subject: [PATCH 076/159] [docs] Compress html docs and speed up deduplication --- .github/workflows/deploy-docs.yml | 4 + tools/scripts/docs_modm_io_generator.py | 144 +++++++++++++---------- tools/scripts/docs_modm_io_index.html.in | 2 +- 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index b3dc740369..965863efe9 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -68,6 +68,10 @@ jobs: export TERM=xterm-256color export COLUMNS=120 python3 tools/scripts/docs_modm_io_generator.py -c -j4 -d + - name: Size of documentation archive + if: always() + run: | + ls -lh modm-api-docs.tar.gz - name: Upload api documentation to docs.modm.io if: always() env: diff --git a/tools/scripts/docs_modm_io_generator.py b/tools/scripts/docs_modm_io_generator.py index 684014a725..fb7266b249 100755 --- a/tools/scripts/docs_modm_io_generator.py +++ b/tools/scripts/docs_modm_io_generator.py @@ -13,8 +13,9 @@ import os import sys import json +import gzip import shutil -import zipfile +import hashlib import tempfile import argparse import datetime @@ -71,7 +72,8 @@ def get_targets(): elif target.platform == "sam": short_id.naming_schema = "{platform}{family}{series}" - short_id.set("platform", target.platform) # invalidate caches + # invalidate id cache due to lack of proper API + short_id.set("platform", target.platform) minimal_targets[short_id.string].append(target) target_list = [] @@ -99,15 +101,15 @@ def get_targets(): def main(): parser = argparse.ArgumentParser() test_group = parser.add_mutually_exclusive_group() - test_group.add_argument("--test", "-t", action='store_true', help="Test mode: generate only a few targets. List includes targets with multiple board modules.") - test_group.add_argument("--test2", "-t2", action='store_true', help="Test mode: generate only a few targets. List has targets from the real target list.") + test_group.add_argument("--test", "-t", action="store_true", help="Test mode: generate only a few targets. List includes targets with multiple board modules.") + test_group.add_argument("--test2", "-t2", action="store_true", help="Test mode: generate only a few targets. List has targets from the real target list.") parser.add_argument("--jobs", "-j", type=int, default=2, help="Number of parallel doxygen processes") - parser.add_argument("--local-temp", "-l", action='store_true', help="Create temporary directory inside current working directory") + parser.add_argument("--local-temp", "-l", action="store_true", help="Create temporary directory inside current working directory") group = parser.add_mutually_exclusive_group() - group.add_argument("--compress", "-c", action='store_true', help="Compress output into gzip archive") + group.add_argument("--compress", "-c", action="store_true", help="Compress output into gzip archive") group.add_argument("--output", "-o", type=str, help="Output directory") - parser.add_argument("--overwrite", "-f", action='store_true', help="Overwrite existing data in output directory (Removes all files from output directory.)") - parser.add_argument("--deduplicate", "-d", action='store_true', help="Deduplicate identical files with symlinks.") + parser.add_argument("--overwrite", "-f", action="store_true", help="Overwrite existing data in output directory (Removes all files from output directory.)") + parser.add_argument("--deduplicate", "-d", action="store_true", help="Deduplicate identical files with symlinks.") parser.add_argument("--target-job", help="Create a single target from job string.") args = parser.parse_args() @@ -136,48 +138,52 @@ def main(): with tempfile.TemporaryDirectory(dir=temp_dir) as tempdir: tempdir = Path(tempdir) modm_path = os.path.abspath(os.path.dirname(sys.argv[0]) + "/../..") - print("Modm Path: {}".format(modm_path)) - print("Temporary directory: {}".format(str(tempdir))) + print(f"Modm Path: {modm_path}") + print(f"Temporary directory: {tempdir}") output_dir = (tempdir / "output") (output_dir / "develop/api").mkdir(parents=True) os.chdir(tempdir) print("Starting to generate documentation...") template_overview(output_dir, device_list, board_list, template_path) - print("... for {} devices, estimated memory footprint is {} MB".format(len(device_list) + len(board_list), (len(device_list)*70)+2000)) + print(f"... for {len(device_list) + len(board_list)} devices, estimated memory footprint is {len(device_list)*70+2000} MB") with ThreadPool(args.jobs) as pool: # We can only pass one argument to pool.map - devices = [f"python3 {filepath} --target-job '{modm_path}|{tempdir}|{dev}||{args.deduplicate}'" for dev in device_list] - devices += [f"python3 {filepath} --target-job '{modm_path}|{tempdir}|{dev}|{brd}|{args.deduplicate}'" for (brd, dev) in board_list] - results = pool.map(lambda d: subprocess.run(d, shell=True).returncode, list(set(devices))) - # output_dir.rename(cwd / 'modm-api-docs') + devices = [f'python3 {filepath} --target-job "{modm_path}|{tempdir}|{dev}||{args.deduplicate}|{args.compress}"' for dev in device_list] + devices += [f'python3 {filepath} --target-job "{modm_path}|{tempdir}|{dev}|{brd}|{args.deduplicate}|{args.compress}"' for (brd, dev) in board_list] + devices = list(set(devices)) + # Run the first generation first so that the other jobs can already deduplicate properly + results = [subprocess.call(devices[0], shell=True)] + results += pool.map(lambda d: subprocess.call(d, shell=True), devices[1:]) + # remove all the hash files + for file in (output_dir / "develop/api").glob("*.hash"): + file.unlink() if args.compress: print("Zipping docs ...") - # Zipping may take more than 10 minutes - os.system(f"(cd {str(output_dir)} && {'g' if is_running_on_macos else ''}tar --checkpoint=.100 -czf {str(cwd / 'modm-api-docs.tar.gz')} .)") - # shutil.make_archive(str(cwd / 'modm-api-docs'), 'gztar', str(output_dir)) + # Zipping is *much* faster via command line than via python! + tar = "gtar" if is_running_on_macos else "tar" + zip_cmd = f"(cd {str(output_dir)} && {tar} --checkpoint=.100 -czf {str(cwd)}/modm-api-docs.tar.gz .)" + subprocess.call(zip_cmd, shell=True) else: if args.overwrite and final_output_dir.exists(): for i in final_output_dir.iterdir(): - print('Removing {}'.format(i)) + print(f"Removing {i}") if i.is_dir(): shutil.rmtree(i) else: os.remove(i) - print('Moving {} -> {}'.format(output_dir, final_output_dir)) - #shutil.move(str(output_dir) + '/', str(final_output_dir)) print(f"Moving {output_dir} -> {final_output_dir}") output_dir.rename(final_output_dir) - return results.count(0) == len(results) + return len(results) - results.count(0) def create_target(argument): - modm_path, tempdir, device, board, deduplicate = argument.split("|") + modm_path, tempdir, device, board, deduplicate, compress = argument.split("|") tempdir = Path(tempdir) output_dir = board if board else device try: - print("Generating documentation for {} ...".format(output_dir)) + print(f"Generating documentation for {output_dir}...") - options = ["modm:target={0}".format(device)] + options = [f"modm:target={device}"] if device.startswith("at"): options.append("modm:platform:core:f_cpu=16000000") builder = lbuild.api.Builder(options=options) @@ -185,7 +191,7 @@ def create_target(argument): modules = sorted(builder.parser.modules.keys()) if board: - chosen_board = "modm:board:{}".format(board) + chosen_board = f"modm:board:{board}" else: # Only allow the first board module to be built (they overwrite each others files) chosen_board = next((m for m in modules if ":board:" in m), None) @@ -200,51 +206,71 @@ def create_target(argument): builder.build(output_dir, modules) - print('Executing: (cd {}/modm/docs/ && doxypress doxypress.json)'.format(output_dir)) - retval = os.system('(cd {}/modm/docs/ && doxypress doxypress.json > /dev/null 2>&1)'.format(output_dir)) + print(f"Executing: (cd {output_dir}/modm/docs/ && doxypress doxypress.json)") + retval = subprocess.call(f"(cd {output_dir}/modm/docs/ && doxypress doxypress.json > /dev/null 2>&1)", shell=True) + # retval = subprocess.call(f"(cd {output_dir}/modm/docs/ && doxygen doxyfile.cfg > /dev/null 2>&1)", shell=True) if retval != 0: - print("Error {} generating documentation for device {}.".format(retval, output_dir)) + print(f"Error {retval} generating documentation for device {output_dir}.") return False - print("Finished generating documentation for device {}.".format(output_dir)) + print(f"Finished generating documentation for device {output_dir}.") srcdir = (tempdir / output_dir / "modm/docs/html") - destdir = tempdir / 'output/develop/api' / output_dir + destdir = tempdir / "output/develop/api" / output_dir if deduplicate == "True": - print("Deduplicating files for {}...".format(device)) - symlinks = defaultdict(list) - for file in (tempdir / 'output').rglob('*'): - if file.is_dir() or file.is_symlink(): continue; - key = file.relative_to(tempdir).parts[4:] - if key: - symlinks[os.path.join(*key)].append(file) + print(f"Deduplicating files for {device}...") + # Find and build the hash symlink database + hashdb = {} + for hashes in tempdir.glob("output/develop/api/*.hash"): + for line in hashes.read_text().splitlines(): + fhash, path = line.split(" ", 1) + hashdb[fhash] = os.path.join(hashes.stem, path) + # Generate a list of files and replace them with symlinks + our_hashdb = {} + # symlinks = {} dot_counter = 0 - for file in srcdir.rglob('*'): + for file in srcdir.rglob("*"): if file.is_dir(): print(end="", flush=True) continue - key = str(file.relative_to(srcdir)) - if key in symlinks: - for kfile in symlinks[key]: - symlinks[hash(kfile.read_bytes())].append(kfile) - del symlinks[key] - fhash = hash(file.read_bytes()) - if fhash in symlinks: - dot_counter += 1 - if dot_counter % 30 == 0: print(".", end="") - rpath = symlinks[fhash][0].relative_to(tempdir / 'output/develop/api') - lpath = os.path.relpath(srcdir, file.parent) - sympath = os.path.join("..", lpath, rpath) - # print("Linking {} -> {}".format(file.relative_to(srcdir), sympath)) + dot_counter += 1 + if dot_counter % 30 == 0: print(".", end="") + file_bytes = file.read_bytes() + if compress == "True": + cfile = file.with_suffix(file.suffix + ".gz") + file_bytes = gzip.compress(file_bytes, mtime=0) + cfile.write_bytes(file_bytes) + file.unlink() + file = cfile + relpath = file.relative_to(srcdir) + fhash = hashlib.md5(file_bytes).hexdigest() + if (rpath := hashdb.get(fhash)) is not None: + # Previously seen file can be symlinked + lpath = os.path.relpath(srcdir.parent, file.parent) + sympath = os.path.join(lpath, rpath) + # symlinks[relpath] = sympath file.unlink() file.symlink_to(sympath) + # print(f"Symlinking {file.relative_to(srcdir)} to {sympath}") + else: + # This is a new file, store it in our hashdb + our_hashdb[fhash] = relpath + + # Write the symlink file + # if symlinks: + # lines = [f"{path} -> {sympath}" for path, sympath in symlinks.items()] + # (srcdir / "symlinks.txt").write_text("\n".join(lines)) + # Write out our hashdb + if our_hashdb: + lines = [f"{fhash} {relpath}" for fhash, relpath in our_hashdb.items()] + destdir.with_suffix(".hash").write_text("\n".join(lines)) # Only move folder *after* deduplication to prevent race condition with file.unlink() print(f"\nMoving {srcdir.relative_to(tempdir)} -> {destdir.relative_to(tempdir)}", flush=True) srcdir.rename(destdir) return True except Exception as e: - print("Error generating documentation for device {}: {}".format(output_dir, e)) + print(f"Error generating documentation for device {output_dir}: {e}") return False @@ -255,18 +281,14 @@ def template_overview(output_dir, device_list, board_list, template_path): date=datetime.datetime.now().strftime("%d.%m.%Y, %H:%M"), num_devices=len(device_list), num_boards=len(board_list)) - with open(str(output_dir) + "/index.html","w+") as f: - f.write(html) + (output_dir / "index.html").write_text(html) json_data = { "devices": [str(d).upper() for d in device_list] + [rename_board(b) for (b,_) in board_list], "name2board": [{rename_board(b): b} for (b,_) in board_list], } - with open(str(output_dir) + "/develop/targets.json","w+") as outfile: + with (output_dir / "develop/targets.json").open("w+", encoding="UTF-8") as outfile: json.dump(json_data, outfile) - with open(str(output_dir) + "/robots.txt","w+") as f: - robots_txt = "User-agent: *\n" - f.write(robots_txt) - + (output_dir / "robots.txt").write_text("User-agent: *\n") if __name__ == "__main__": - exit(0 if main() else -1) + exit(main()) diff --git a/tools/scripts/docs_modm_io_index.html.in b/tools/scripts/docs_modm_io_index.html.in index fb727b7f49..4c0d3e68d8 100644 --- a/tools/scripts/docs_modm_io_index.html.in +++ b/tools/scripts/docs_modm_io_index.html.in @@ -219,7 +219,7 @@ function showDocumentation() { return; } n2b = name2board[targetinput.value] - var url ="/" + releaseinput.value + "/api/" + (n2b ? n2b : targetinput.value).toLowerCase() + "/"; + var url ="/" + releaseinput.value + "/api/" + (n2b ? n2b : targetinput.value).toLowerCase() + "/index.html"; location.href = url; } targetinput.addEventListener("input", function(event) { From c66f80b9f9e644f119a7d779d0737c9b3660f0e4 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 23 Dec 2023 17:30:34 +0100 Subject: [PATCH 077/159] [ext] Update submodules --- ext/etlcpp/etl | 2 +- ext/lvgl/lvgl | 2 +- ext/modm-devices | 2 +- ext/nanopb/nanopb | 2 +- ext/st/stm32 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/etlcpp/etl b/ext/etlcpp/etl index 894ce959fe..04e70d7955 160000 --- a/ext/etlcpp/etl +++ b/ext/etlcpp/etl @@ -1 +1 @@ -Subproject commit 894ce959fe8fd9a1ccb3b04a060901919df8886b +Subproject commit 04e70d7955e67888b2799984a3469e16a6f00f53 diff --git a/ext/lvgl/lvgl b/ext/lvgl/lvgl index 88f2e40bcf..22f131620a 160000 --- a/ext/lvgl/lvgl +++ b/ext/lvgl/lvgl @@ -1 +1 @@ -Subproject commit 88f2e40bcf9494dee2d71b11a377c039ef83c372 +Subproject commit 22f131620a144d0aa813f024322b940d806def64 diff --git a/ext/modm-devices b/ext/modm-devices index 9186079eed..9079ec5722 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit 9186079eedde2108d8cf0387e83d73a1b5647179 +Subproject commit 9079ec5722d523ecff2585b3e6a651aea31961d6 diff --git a/ext/nanopb/nanopb b/ext/nanopb/nanopb index a3a3a2fda0..89faeb6a00 160000 --- a/ext/nanopb/nanopb +++ b/ext/nanopb/nanopb @@ -1 +1 @@ -Subproject commit a3a3a2fda0a8f5d80642adfceb0c4bbf9db02d8e +Subproject commit 89faeb6a0074f8aff0c4f84ff22191e45779d591 diff --git a/ext/st/stm32 b/ext/st/stm32 index 0f82e9d808..e52d3949b6 160000 --- a/ext/st/stm32 +++ b/ext/st/stm32 @@ -1 +1 @@ -Subproject commit 0f82e9d808e36d45dd39c49db47d963826c2a354 +Subproject commit e52d3949b6a2497a3dbcd62e0fe78be2a1e0d341 From 84463aefca9b2c393727a134783919c17ed9aeea Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Mon, 1 Jan 2024 20:05:13 +0100 Subject: [PATCH 078/159] [release] Update changelog for 2023q4 release --- .mailmap | 1 + CHANGELOG.md | 75 ++++++++++++++++++++++++++++++++++++++++ docs/mkdocs.yml | 2 +- docs/release/2023q4.md | 64 ++++++++++++++++++++++++++++++++++ tools/scripts/authors.py | 5 +-- 5 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 docs/release/2023q4.md diff --git a/.mailmap b/.mailmap index 432145a100..ea60683275 100644 --- a/.mailmap +++ b/.mailmap @@ -34,6 +34,7 @@ Henrik Hose Jacob Schultz Andersen Jakob Riepler Jeff McBride +Jens Böckmann Jonas Kazem Andersen Julia Gutheil Jörg Hoffmann diff --git a/CHANGELOG.md b/CHANGELOG.md index 4afea26ac5..828bae4e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,72 @@ pay attention to. Medium impact changes are also worth looking at. +## 2024-01-01: 2023q4 release + +This release covers everything from 2023-10-01 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Features: + +- STM32H7 SPI driver with DMA support. + +Integrated Projects: + +- ETL upgraded to v20.38.10. +- LVGL upgraded to v8.3.11. +- Nanopb upgraded to v0.4.8. +- STM32G0 headers upgraded to v1.4.3. +- STM32F4 headers upgraded to v2.6.9. +- STM32U5 headers upgraded to v1.3.1. + +Fixes: + +- Fix SPI clocks on Nucleo-H723ZG boards. +- Do not require protothreads to use SPI with fibers. +- Place main stack in DMA-able SRAM on STM32H7. + +New device drivers: + +- BMI088 IMU driver as [`modm:driver:bmi088`][]. + +Known bugs: + +- Fibers are not implemented for ARM64 targets. See [#1111][]. +- OpenOCD cannot enable SWO on STM32H7 targets. See [#1079][]. +- STM32F7: D-Cache not enabled by default. See [#485][]. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See [#285][]. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See [#310][]. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See [#591][]. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Christopher Durand ([@chris-durand][]) +- Jens Böckmann ([@jensboe][]) 🎉 +- Niklas Hauser ([@salkinium][]) +- Sergey Pluzhnikov ([@ser-plu][]) + +PR [#1112][] -> [2023q4][]. + +
+Detailed changelog + +#### 2023-10-04: Add STM32H7 SPI driver with DMA support + +PR [#1052][] -> [53796b0][]. +Tested in hardware by [@chris-durand][]. + +#### 2023-10-05: Add BMI088 driver + +PR [#1052][] -> [a771042][]. +Tested in hardware by [@chris-durand][]. + +
+ + ## 2023-10-01: 2023q3 release This release covers everything from 2023-07-01 and has been tested with avr-gcc @@ -2769,6 +2835,7 @@ Please note that contributions from xpcc were continuously ported to modm. [2023q1]: https://github.com/modm-io/modm/releases/tag/2023q1 [2023q2]: https://github.com/modm-io/modm/releases/tag/2023q2 [2023q3]: https://github.com/modm-io/modm/releases/tag/2023q3 +[2023q4]: https://github.com/modm-io/modm/releases/tag/2023q4 [@19joho66]: https://github.com/19joho66 [@ASMfreaK]: https://github.com/ASMfreaK @@ -2799,6 +2866,7 @@ Please note that contributions from xpcc were continuously ported to modm. [@henrikssn]: https://github.com/henrikssn [@hshose]: https://github.com/hshose [@jasa]: https://github.com/jasa +[@jensboe]: https://github.com/jensboe [@klsc-zeat]: https://github.com/klsc-zeat [@lgili]: https://github.com/lgili [@linasnikis]: https://github.com/linasnikis @@ -2869,6 +2937,7 @@ Please note that contributions from xpcc were continuously ported to modm. [`modm:driver:apa102`]: https://modm.io/reference/module/modm-driver-apa102 [`modm:driver:at24mac402`]: https://modm.io/reference/module/modm-driver-at24mac402 [`modm:driver:block.device:spi.stack.flash`]: https://modm.io/reference/module/modm-driver-block-device-spi-stack-flash +[`modm:driver:bmi088`]: https://modm.io/reference/module/modm-driver-bmi088 [`modm:driver:bno055`]: https://modm.io/reference/module/modm-driver-bno055 [`modm:driver:cat24aa`]: https://modm.io/reference/module/modm-driver-cat24aa [`modm:driver:cycle_counter`]: https://modm.io/reference/module/modm-driver-cycle_counter @@ -2927,11 +2996,15 @@ Please note that contributions from xpcc were continuously ported to modm. [#1049]: https://github.com/modm-io/modm/pull/1049 [#1050]: https://github.com/modm-io/modm/pull/1050 [#1051]: https://github.com/modm-io/modm/pull/1051 +[#1052]: https://github.com/modm-io/modm/pull/1052 [#1053]: https://github.com/modm-io/modm/pull/1053 [#1054]: https://github.com/modm-io/modm/pull/1054 [#1063]: https://github.com/modm-io/modm/pull/1063 [#1066]: https://github.com/modm-io/modm/pull/1066 +[#1079]: https://github.com/modm-io/modm/pull/1079 [#1088]: https://github.com/modm-io/modm/pull/1088 +[#1111]: https://github.com/modm-io/modm/pull/1111 +[#1112]: https://github.com/modm-io/modm/pull/1112 [#118]: https://github.com/modm-io/modm/pull/118 [#122]: https://github.com/modm-io/modm/pull/122 [#132]: https://github.com/modm-io/modm/pull/132 @@ -3212,6 +3285,7 @@ Please note that contributions from xpcc were continuously ported to modm. [516b2b3]: https://github.com/modm-io/modm/commit/516b2b3 [517bd84]: https://github.com/modm-io/modm/commit/517bd84 [5332765]: https://github.com/modm-io/modm/commit/5332765 +[53796b0]: https://github.com/modm-io/modm/commit/53796b0 [544f6d3]: https://github.com/modm-io/modm/commit/544f6d3 [55d5911]: https://github.com/modm-io/modm/commit/55d5911 [564effa]: https://github.com/modm-io/modm/commit/564effa @@ -3288,6 +3362,7 @@ Please note that contributions from xpcc were continuously ported to modm. [a38feca]: https://github.com/modm-io/modm/commit/a38feca [a607613]: https://github.com/modm-io/modm/commit/a607613 [a6b4186]: https://github.com/modm-io/modm/commit/a6b4186 +[a771042]: https://github.com/modm-io/modm/commit/a771042 [a8edbe8]: https://github.com/modm-io/modm/commit/a8edbe8 [ab9bcee]: https://github.com/modm-io/modm/commit/ab9bcee [ac46099]: https://github.com/modm-io/modm/commit/ac46099 diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 52c6676023..30455fdb8c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,7 +1,7 @@ site_name: 'modm barebone embedded library' site_description: 'A modular C++23 library generator for barebone embedded programming' site_author: 'Niklas Hauser' -site_url: 'http://modm.io' +site_url: 'https://modm.io' # Repository repo_name: modm-io/modm diff --git a/docs/release/2023q4.md b/docs/release/2023q4.md new file mode 100644 index 0000000000..25b3ad50cf --- /dev/null +++ b/docs/release/2023q4.md @@ -0,0 +1,64 @@ +## 2024-01-01: 2023q4 release + +This release covers everything from 2023-10-01 and has been tested with avr-gcc +v12.2.0 from Upstream and arm-none-eabi-gcc v12.2.1 from xpack. + +Features: + +- STM32H7 SPI driver with DMA support. + +Integrated Projects: + +- ETL upgraded to v20.38.10. +- LVGL upgraded to v8.3.11. +- Nanopb upgraded to v0.4.8. +- STM32G0 headers upgraded to v1.4.3. +- STM32F4 headers upgraded to v2.6.9. +- STM32U5 headers upgraded to v1.3.1. + +Fixes: + +- Fix SPI clocks on Nucleo-H723ZG boards. +- Do not require protothreads to use SPI with fibers. +- Place main stack in DMA-able SRAM on STM32H7. + +New device drivers: + +- BMI088 IMU driver as `modm:driver:bmi088`. + +Known bugs: + +- Fibers are not implemented for ARM64 targets. See #1111. +- OpenOCD cannot enable SWO on STM32H7 targets. See #1079. +- STM32F7: D-Cache not enabled by default. See #485. +- `lbuild build` and `lbuild clean` do not remove all previously generated files + when the configuration changes. See #285. +- Generating modm on Windows creates paths with `\` that are not compatible with + Unix. See #310. +- `arm-none-eabi-gdb` TUI and GDBGUI interfaces are not supported on Windows. + See #591. + +Many thanks to all our contributors. +A special shoutout to first timers 🎉: + +- Christopher Durand (@chris-durand) +- Jens Böckmann (@jensboe) 🎉 +- Niklas Hauser (@salkinium) +- Sergey Pluzhnikov (@ser-plu) + +PR #1112 -> 2023q4. + +
+Detailed changelog + +#### 2023-10-04: Add STM32H7 SPI driver with DMA support + +PR #1052 -> 53796b0. +Tested in hardware by @chris-durand. + +#### 2023-10-05: Add BMI088 driver + +PR #1052 -> a771042. +Tested in hardware by @chris-durand. + +
diff --git a/tools/scripts/authors.py b/tools/scripts/authors.py index 6cb1dcad9d..2b9cd86eb5 100755 --- a/tools/scripts/authors.py +++ b/tools/scripts/authors.py @@ -17,6 +17,7 @@ import re author_handles = { + "Alexander Solovets": "mbait", "Amar": "fb39ca4", "Amarok McLion": "amarokmclion", "Andre Gilerson": "AndreGilerson", @@ -42,12 +43,14 @@ "Jacob Schultz Andersen": "jasa", "Jakob Riepler": "XDjackieXD", "Jeff McBride": "mcbridejc", + "Jens Böckmann": "jensboe", "Jonas Kazem Andersen": "JKazem", "Jonas Kazem Andersen": "JKazem", "Julia Gutheil": None, "Jörg Hoffmann": "19joho66", "Kaelin Laundry": "WasabiFan", "Kevin Läufer": "ekiwi", + "Klaus Schnass": "klsc-zeat", "Linas Nikiperavicius": "linasnikis", "Lucas Mösch": "lmoesch", "Luiz Gili": "lgili", @@ -89,8 +92,6 @@ "Vivien Henry": "lukh", "Zawadniak Pedro": "PDR5", "Álan Crístoffer": "acristoffers", - "Klaus Schnass": "klsc-zeat", - "Alexander Solovets": "mbait", } def get_author_log(since = None, until = None, handles = False, count = False): From 6b1f0010076703b8c41f3107b8baf95d50661288 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 7 Aug 2023 18:58:12 +0200 Subject: [PATCH 079/159] [stm32] Fix Rcc::enable for H7 comparator --- src/modm/platform/clock/stm32/module.lb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 6dff923a6f..f5460754ed 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -155,6 +155,13 @@ def build(env): if "Fdcan1" in all_peripherals and per == "FDCAN": per = "FDCAN1" nper = "FDCAN" + # COMP12 + if "Comp1" in all_peripherals and per == "COMP12": + per = "COMP1" + nper = "COMP12" + if "Comp2" in all_peripherals and per == "COMP12": + per = "COMP2" + nper = "COMP12" # DAC if "Dac1" in all_peripherals and per == "DAC": per = "DAC1" From 32fa118033dad1551fdf37b9652ddac1136ecb66 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 7 Aug 2023 19:00:26 +0200 Subject: [PATCH 080/159] [stm32] Add advanced timer isOutputEnabled() --- src/modm/platform/timer/stm32/advanced.hpp.in | 6 ++++++ src/modm/platform/timer/stm32/general_purpose.hpp.in | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/modm/platform/timer/stm32/advanced.hpp.in b/src/modm/platform/timer/stm32/advanced.hpp.in index 3d2ca0d6d3..7884521bf5 100644 --- a/src/modm/platform/timer/stm32/advanced.hpp.in +++ b/src/modm/platform/timer/stm32/advanced.hpp.in @@ -255,6 +255,12 @@ public: TIM{{ id }}->BDTR &= ~(TIM_BDTR_MOE); } + static inline bool + isOutputEnabled() + { + return (TIM{{ id }}->BDTR & TIM_BDTR_MOE); + } + /* * Enable/Disable automatic set of MOE bit at the next update event */ diff --git a/src/modm/platform/timer/stm32/general_purpose.hpp.in b/src/modm/platform/timer/stm32/general_purpose.hpp.in index d4826f44f2..034b520e2e 100644 --- a/src/modm/platform/timer/stm32/general_purpose.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.hpp.in @@ -289,6 +289,12 @@ public: TIM{{ id }}->BDTR &= ~(TIM_BDTR_MOE); } + static inline bool + isOutputEnabled() + { + return (TIM{{ id }}->BDTR & TIM_BDTR_MOE); + } + /* * Enable/Disable automatic set of MOE bit at the next update event */ From c7d7dafb15f593bc9d15c891cc6ca337961a5a40 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 4 Sep 2023 11:59:56 +0200 Subject: [PATCH 081/159] [tools] Suppress GCC ABI warning Disable warning "note: parameter passing for argument of type '...' when C++17 is enabled changed to match C++14 in GCC 10.1" --- tools/build_script_generator/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/build_script_generator/common.py b/tools/build_script_generator/common.py index cc0d514c0e..796994e5b1 100644 --- a/tools/build_script_generator/common.py +++ b/tools/build_script_generator/common.py @@ -274,6 +274,7 @@ def common_compiler_flags(compiler, target): # "-Wold-style-cast", "-fstrict-enums", "-std=c++23", + "-Wno-psabi", "-Wno-volatile", # volatile is deprecated in C++20 but lots of our external code uses it... # "-pedantic", ] From 088a8483a543a15c721e692f7c3fdec420639df7 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 4 Sep 2023 12:02:49 +0200 Subject: [PATCH 082/159] [tools] Fix RTT logger if data structure is not in RAM with lowest address --- tools/build_script_generator/module.lb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build_script_generator/module.lb b/tools/build_script_generator/module.lb index c018c3f169..42d129ed70 100644 --- a/tools/build_script_generator/module.lb +++ b/tools/build_script_generator/module.lb @@ -257,7 +257,7 @@ def post_build(env): has_rtt = env.has_module(":platform:rtt") env.substitutions["has_rtt"] = has_rtt if has_rtt: - env.substitutions["main_ram"] = linkerscript.get("cont_ram_regions", [{"start": 0x20000000, "size": 4096}])[0] + env.substitutions["main_ram"] = linkerscript.get("cont_ram", {"start": 0x20000000, "size": 4096}) env.substitutions["rtt_channels"] = len(env.get(":platform:rtt:buffer.tx", [])) env.template("openocd.cfg.in") From ea1e6ff6828f6ce8ee5390d9f29481873acce32a Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 4 Sep 2023 12:14:34 +0200 Subject: [PATCH 083/159] [test] Allow unit test case names in snake_case (test_foo()) --- tools/modm_tools/unit_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/modm_tools/unit_test.py b/tools/modm_tools/unit_test.py index 2f2437ed17..9a06f0e2d5 100644 --- a/tools/modm_tools/unit_test.py +++ b/tools/modm_tools/unit_test.py @@ -91,7 +91,7 @@ def extract_tests(headers): name = name[0] functions = re.findall( - r"void\s+(test[A-Z]\w*)\s*\([\svoid]*\)\s*;", content) + r"void\s+(test[_a-zA-Z]\w*)\s*\([\svoid]*\)\s*;", content) if not functions: print("No tests found in {}!".format(header)) From 7bd22ce73e1ee43c04cbf60f0b5f8aa8cc999848 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 5 Sep 2023 20:32:17 +0200 Subject: [PATCH 084/159] [tools] Treat missing return in function as error --- tools/build_script_generator/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/build_script_generator/common.py b/tools/build_script_generator/common.py index 796994e5b1..bc0948fa73 100644 --- a/tools/build_script_generator/common.py +++ b/tools/build_script_generator/common.py @@ -213,6 +213,7 @@ def common_compiler_flags(compiler, target): "-Werror=maybe-uninitialized", "-Werror=overflow", "-Werror=sign-compare", + "-Werror=return-type", "-Wextra", "-Wlogical-op", "-Wpointer-arith", From 08479cd1f5c99cc00fa201e81c216fafa54e4f99 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Wed, 6 Sep 2023 17:13:54 +0200 Subject: [PATCH 085/159] [stm32] Fix STM32G0 DMA --- src/modm/platform/dma/stm32/dma.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index 9da23cf00b..e2bbb4fe94 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -73,7 +73,7 @@ public: %% endif } %% endif -%% if dmaType in ["stm32-mux"]: +%% if dmaType in ["stm32-mux"] and target.family != "g0": Rcc::enable(); %% endif } From 72074728175d2cb8831bffbc2d97b06f69d2e0f4 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 21 Sep 2023 02:27:54 +0200 Subject: [PATCH 086/159] [stm32] Fix reconfiguring DMAMUX requests Configuring a DMAMUX request was only working once because the bits were not properly cleared. --- src/modm/platform/dma/stm32/dma.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index e2bbb4fe94..5a672d8c6b 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -295,7 +295,7 @@ public: %% endif })->muxChannel; auto* channel = DMAMUX1_Channel0 + muxChannel; - channel->CCR = (channel->CCR & DMAMUX_CxCR_DMAREQ_ID) | uint32_t(dmaRequest); + channel->CCR = (channel->CCR & ~DMAMUX_CxCR_DMAREQ_ID) | uint32_t(dmaRequest); %% elif dmaType in ["stm32-stream-channel"] DMA_Channel_TypeDef *Channel = reinterpret_cast(CHANNEL_BASE); Channel->CR = (Channel->CR & ~DMA_SxCR_CHSEL_Msk) | uint32_t(dmaRequest); From 5441d0871c83444e39093b0a00d3d17df078cf50 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Fri, 6 Oct 2023 20:25:17 +0200 Subject: [PATCH 087/159] [stm32] Fix STM32H7 32-bit timer counter size --- src/modm/platform/timer/stm32/general_purpose.hpp.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modm/platform/timer/stm32/general_purpose.hpp.in b/src/modm/platform/timer/stm32/general_purpose.hpp.in index 034b520e2e..3e40462598 100644 --- a/src/modm/platform/timer/stm32/general_purpose.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.hpp.in @@ -110,8 +110,8 @@ public: }; // This type is the internal size of the counter. -%% if id in [2, 5] and (target["family"] in ["f2", "f3", "f4", "f7", "l1", "l4", "g4"]) - // Timer 2 and 5 are the only one which have the size of 32 bit +%% if id in [2, 5, 23, 24] and (target["family"] in ["f2", "f3", "f4", "f7", "l1", "l4", "g4", "h7"]) + // Timer 2, 5, 23 and 24 are the only ones which have a 32 bit counter using Value = uint32_t; %% else From 5e3acab574e962832ff6d170c46d4b6c06b66219 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 16 Nov 2023 16:21:12 +0100 Subject: [PATCH 088/159] [stm32] Fix H7 ADC asynchronous clock --- src/modm/platform/adc/stm32f3/adc.hpp.in | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index 947d7f5238..e2814af448 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -163,8 +163,8 @@ public: // ADCs clock source selection enum class ClockSource : uint32_t { - NoClock = 0, // No clock selected. %% if target["family"] in ["g4"] + NoClock = 0, // No clock selected. %% if id in [1, 2] Pll = RCC_{{ ccipr }}_ADC12SEL_0, // PLL “P” clock selected as ADC clock SystemClock = RCC_{{ ccipr }}_ADC12SEL_1 , // System clock selected as ADCs clock @@ -172,7 +172,13 @@ public: Pll = RCC_{{ ccipr }}_ADC345SEL_0, // PLL “P” clock selected as ADC clock SystemClock = RCC_{{ ccipr }}_ADC345SEL_1 , // System clock selected as ADCs clock %% endif +%% elif target["family"] in ["h7"] + Pll2P = 0, + Pll3R = RCC_{{ ccipr }}_ADCSEL_0, + PerClk = RCC_{{ ccipr }}_ADCSEL_1, + NoClock = PerClk, // for compatibility if sync. clock is used and setting is ignored %% else + NoClock = 0, // No clock selected. PllSai1 = RCC_{{ ccipr }}_ADCSEL_0, // PLLSAI1 "R" clock (PLLADC1CLK) selected as ADCs clock %% if target["family"] != "l5" PllSai2 = RCC_{{ ccipr }}_ADCSEL_1, // PLLSAI2 "R" clock (PLLADC2CLK) selected as ADCs clock @@ -337,7 +343,11 @@ public: static inline void initialize( const ClockMode clk = ClockMode::DoNotChange, %% if clock_mux +%% if target["family"] == "h7" + const ClockSource clk_src = ClockSource::PerClk, +%% else const ClockSource clk_src = ClockSource::SystemClock, +%% endif %% endif const Prescaler pre = Prescaler::Disabled, const CalibrationMode cal = CalibrationMode::DoNotCalibrate, From 59437d1604cdc6a7c6038984fbeed4dd289dffcc Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 16 Nov 2023 16:21:40 +0100 Subject: [PATCH 089/159] [stm32] Fix sampling time configuration in F3 ADC driver --- src/modm/platform/adc/stm32f3/adc_impl.hpp.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index 45a3720009..b273c02852 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -195,14 +195,14 @@ modm::platform::Adc{{ id }}::configureChannel(Channel channel, uint32_t tmpreg = 0; if (static_cast(channel) < 10) { tmpreg = ADC{{ id }}->SMPR1 - & ((~ADC_SMPR1_SMP0) << (static_cast(channel) * 3)); + & ~((ADC_SMPR1_SMP0) << (static_cast(channel) * 3)); tmpreg |= static_cast(sampleTime) << (static_cast(channel) * 3); ADC{{ id }}->SMPR1 = tmpreg; } else { tmpreg = ADC{{ id }}->SMPR2 - & ((~ADC_SMPR2_SMP10) << ((static_cast(channel)-10) * 3)); + & ~((ADC_SMPR2_SMP10) << ((static_cast(channel)-10) * 3)); tmpreg |= static_cast(sampleTime) << ((static_cast(channel)-10) * 3); ADC{{ id }}->SMPR2 = tmpreg; From 8bcbe253227bda581c62feecc448a5dd77daf5d9 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 16 Nov 2023 18:05:04 +0100 Subject: [PATCH 090/159] [stm32] Fix compilation of DMA channel clearInterruptFlags() --- src/modm/platform/dma/stm32/dma.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index 5a672d8c6b..5cab771b7d 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -384,7 +384,7 @@ public: static void clearInterruptFlags(InterruptFlags_t flags = InterruptFlags::All) { - ControlHal::clearInterruptFlags(flags, ChannelID); + ControlHal::clearInterruptFlags(InterruptFlags(flags.value), ChannelID); } /** From 2da6c2f25cdfdb1a12fc6432e6fecaa05b2efeee Mon Sep 17 00:00:00 2001 From: cajt Date: Fri, 5 Jan 2024 19:30:00 +0100 Subject: [PATCH 091/159] [board] Add support for STM32F401 Discovery --- README.md | 29 +-- src/modm/board/disco_f401vc/board.hpp | 268 ++++++++++++++++++++++++++ src/modm/board/disco_f401vc/board.xml | 13 ++ src/modm/board/disco_f401vc/module.lb | 47 +++++ 4 files changed, 343 insertions(+), 14 deletions(-) create mode 100644 src/modm/board/disco_f401vc/board.hpp create mode 100644 src/modm/board/disco_f401vc/board.xml create mode 100644 src/modm/board/disco_f401vc/module.lb diff --git a/README.md b/README.md index a2ad41c854..98297ef321 100644 --- a/README.md +++ b/README.md @@ -622,75 +622,76 @@ We have out-of-box support for many development boards including documentation. DISCO-F100RB DISCO-F303VC +DISCO-F401VC DISCO-F407VG DISCO-F429ZI -DISCO-F469NI +DISCO-F469NI DISCO-F746NG DISCO-F769NI DISCO-L152RC -DISCO-L476VG +DISCO-L476VG FEATHER-M0 FEATHER-M4 FEATHER-RP2040 -MEGA-2560-PRO +MEGA-2560-PRO NUCLEO-F031K6 NUCLEO-F042K6 NUCLEO-F072RB -NUCLEO-F091RC +NUCLEO-F091RC NUCLEO-F103RB NUCLEO-F303K8 NUCLEO-F303RE -NUCLEO-F334R8 +NUCLEO-F334R8 NUCLEO-F401RE NUCLEO-F411RE NUCLEO-F429ZI -NUCLEO-F439ZI +NUCLEO-F439ZI NUCLEO-F446RE NUCLEO-F446ZE NUCLEO-F746ZG -NUCLEO-F767ZI +NUCLEO-F767ZI NUCLEO-G071RB NUCLEO-G431KB NUCLEO-G431RB -NUCLEO-G474RE +NUCLEO-G474RE NUCLEO-H723ZG NUCLEO-H743ZI NUCLEO-L031K6 -NUCLEO-L053R8 +NUCLEO-L053R8 NUCLEO-L152RE NUCLEO-L432KC NUCLEO-L452RE -NUCLEO-L476RG +NUCLEO-L476RG NUCLEO-L496ZG-P NUCLEO-L552ZE-Q NUCLEO-U575ZI-Q -OLIMEXINO-STM32 +OLIMEXINO-STM32 Raspberry Pi Raspberry Pi Pico SAMD21-MINI -SAMD21-XPLAINED-PRO +SAMD21-XPLAINED-PRO SAME54-XPLAINED-PRO SAME70-XPLAINED SAMG55-XPLAINED-PRO -SAMV71-XPLAINED-ULTRA +SAMV71-XPLAINED-ULTRA Smart Response XE STM32-F4VE STM32F030-DEMO -THINGPLUS-RP2040 +THINGPLUS-RP2040 diff --git a/src/modm/board/disco_f401vc/board.hpp b/src/modm/board/disco_f401vc/board.hpp new file mode 100644 index 0000000000..ac0d118e38 --- /dev/null +++ b/src/modm/board/disco_f401vc/board.hpp @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2015-2018, Niklas Hauser + * Copyright (c) 2017, Sascha Schade + * Copyright (c) 2018, Antal Szabó + * Copyright (c) 2024, Carl Treudler + * + * 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_STM32_F401_DISCOVERY_HPP +#define MODM_STM32_F401_DISCOVERY_HPP + +#include +#include +#include +#include + +using namespace modm::platform; + +namespace Board +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using namespace modm::literals; + +/// STM32F401 running at 168MHz generated from the external 8MHz crystal +struct SystemClock +{ + static constexpr uint32_t Frequency = 84_MHz; + static constexpr uint32_t Ahb = Frequency; + static constexpr uint32_t Apb1 = Frequency / 4; + static constexpr uint32_t Apb2 = Frequency / 2; + + static constexpr uint32_t Adc = Apb2; + + static constexpr uint32_t Spi1 = Apb2; + static constexpr uint32_t Spi2 = Apb1; + static constexpr uint32_t Spi3 = Apb1; + static constexpr uint32_t Spi4 = Apb2; + + static constexpr uint32_t Usart1 = Apb2; + static constexpr uint32_t Usart2 = Apb1; + static constexpr uint32_t Usart6 = Apb1; + + static constexpr uint32_t I2c1 = Apb1; + static constexpr uint32_t I2c2 = Apb1; + static constexpr uint32_t I2c3 = Apb1; + + static constexpr uint32_t Apb1Timer = Apb1 * 2; + static constexpr uint32_t Apb2Timer = Apb2 * 2; + static constexpr uint32_t Timer1 = Apb2Timer; + static constexpr uint32_t Timer2 = Apb1Timer; + static constexpr uint32_t Timer3 = Apb1Timer; + static constexpr uint32_t Timer4 = Apb1Timer; + static constexpr uint32_t Timer5 = Apb1Timer; + static constexpr uint32_t Timer6 = Apb1Timer; + static constexpr uint32_t Timer7 = Apb1Timer; + static constexpr uint32_t Timer8 = Apb2Timer; + static constexpr uint32_t Timer9 = Apb2Timer; + static constexpr uint32_t Timer10 = Apb2Timer; + static constexpr uint32_t Timer11 = Apb2Timer; + + static constexpr uint32_t Usb = 48_MHz; + + static bool inline + enable() + { + Rcc::enableExternalCrystal(); // 8MHz + const Rcc::PllFactors pllFactors{ + .pllM = 4, // 8MHz / M=4 -> 2MHz + .pllN = 168, // 2MHz * N=168 -> 336MHz + .pllP = 4, // 336MHz / P=4 -> 84MHz = F_cpu + .pllQ = 7 // 336MHz / Q=7 -> 48MHz = F_usb + }; + Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); + // set flash latency for 84MHz + Rcc::setFlashLatency(); + // switch system clock to PLL output + Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); + Rcc::setAhbPrescaler(Rcc::AhbPrescaler::Div1); + // APB1 has max. 42MHz + // APB2 has max. 84MHz + Rcc::setApb1Prescaler(Rcc::Apb1Prescaler::Div4); + Rcc::setApb2Prescaler(Rcc::Apb2Prescaler::Div2); + // update frequencies for busy-wait delay functions + Rcc::updateCoreFrequency(); + + return true; + } +}; + +using Button = GpioInputA0; +using ClockOut = GpioOutputA8; +using SystemClockOut = GpioOutputC9; + +using LedOrange = GpioOutputD13; // User LED 3 +using LedGreen = GpioOutputD12; // User LED 4 +using LedRed = GpioOutputD14; // User LED 5 +using LedBlue = GpioOutputD15; // User LED 6 + +using Leds = SoftwareGpioPort< LedGreen, LedBlue, LedRed, LedOrange >; +/// @} + +namespace lsm3 +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using Drdy = GpioInputE2; // DRDY [LSM303DLHC_DRDY]: GPXTI2 +using Int1 = GpioInputE4; // MEMS_INT3 [LSM303DLHC_INT1]: GPXTI4 +using Int2 = GpioInputE5; // MEMS_INT4 [LSM303DLHC_INT2]: GPXTI5 + +using Scl = GpioB6; // I2C1_SCL [LSM303DLHC_SCL]: I2C1_SCL +using Sda = GpioB9; // I2C1_SDA [LSM303DLHC_SDA]: I2C1_SDA + +using I2cMaster = I2cMaster1; +using Accelerometer = modm::Lsm303a< I2cMaster >; +/// @} +} + +namespace l3g +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using Int1 = GpioInputE0; // MEMS_INT1 [L3GD20_INT1]: GPXTI0 +using Int2 = GpioInputE1; // MEMS_INT2 [L3GD20_DRDY/INT2]: GPXTI1 + +using Cs = GpioOutputE3; // CS_I2C/SPI [L3GD20_CS_I2C/SPI] +using Sck = GpioOutputA5; // SPI1_SCK [L3GD20_SCL/SPC] +using Mosi = GpioOutputA7; // SPI1_MOSI [L3GD20_SDA/SDI/SDO] +using Miso = GpioInputA6; // SPI1_MISO [L3GD20_SA0/SDO] + +using SpiMaster = SpiMaster1; +using Transport = modm::Lis3TransportSpi< SpiMaster, Cs >; +using Gyroscope = modm::L3gd20< Transport >; +/// @} +} + +namespace cs43 +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using Lrck = GpioOutputA4; // I2S3_WS +using Mclk = GpioOutputC7; // I2S3_MCK +using Sclk = GpioOutputC10; // I2S3_SCK +using Sdin = GpioOutputC12; // I2S3_SD + +using Reset = GpioOutputD4; // Audio_RST +using Scl = GpioB6; // Audio_SCL +using Sda = GpioB9; // Audio_SDA + +using I2cMaster = I2cMaster1; +//using I2sMaster = I2sMaster3; +/// @} +} + + +namespace mp45 +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using Clk = GpioOutputB10; // CLK_IN: I2S2_CK +using Dout = GpioInputC3; // PDM_OUT: I2S2_SD +//using I2sMaster = I2sMaster2; +/// @} +} + + +namespace usb +{ +/// @ingroup modm_board_disco_f401vc +/// @{ +using Vbus = GpioInputA9; // VBUS_FS: USB_OTG_HS_VBUS +using Id = GpioA10; // OTG_FS_ID: USB_OTG_FS_ID +using Dm = GpioA11; // OTG_FS_DM: USB_OTG_FS_DM +using Dp = GpioA12; // OTG_FS_DP: USB_OTG_FS_DP + +using Overcurrent = GpioInputD5; // OTG_FS_OverCurrent +using Power = GpioOutputC0; // OTG_FS_PowerSwitchOn + +using Device = UsbFs; +/// @} +} + + +/// @ingroup modm_board_disco_f401vc +/// @{ +inline void +initialize() +{ + SystemClock::enable(); + SysTickTimer::initialize(); + + Leds::setOutput(modm::Gpio::Low); + + Button::setInput(); +} + + +inline void +initializeLsm3() +{ + lsm3::Int1::setInput(); + lsm3::Int2::setInput(); + lsm3::Drdy::setInput(); + + lsm3::I2cMaster::connect(); + lsm3::I2cMaster::initialize(); +} + +/// not supported yet, due to missing I2S driver +inline void +initializeCs43() +{ +// cs43::Lrck::connect(cs43::I2sMaster::Ws); +// cs43::Mclk::connect(cs43::I2sMaster::Mck); +// cs43::Sclk::connect(cs43::I2sMaster::Ck); +// cs43::Sdin::connect(cs43::I2sMaster::Sd); + + cs43::Reset::setOutput(modm::Gpio::High); + + cs43::I2cMaster::connect(); + cs43::I2cMaster::initialize(); +} + +inline void +initializeL3g() +{ + l3g::Int1::setInput(); + l3g::Int2::setInput(); + l3g::Cs::setOutput(modm::Gpio::High); + + l3g::SpiMaster::connect(); + l3g::SpiMaster::initialize(); + l3g::SpiMaster::setDataMode(l3g::SpiMaster::DataMode::Mode3); +} + +/// not supported yet, due to missing I2S driver +inline void +initializeMp45() +{ +// mp45::Clk::connect(mp45::I2sMaster::Ck); +// mp45::Dout::connect(mp45::I2sMaster::Sd); +} + +inline void +initializeUsbFs(uint8_t priority=3) +{ + usb::Device::initialize(priority); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; +} + +/// @} + +} + +#endif // MODM_STM32_F401_DISCOVERY_HPP diff --git a/src/modm/board/disco_f401vc/board.xml b/src/modm/board/disco_f401vc/board.xml new file mode 100644 index 0000000000..da8885a215 --- /dev/null +++ b/src/modm/board/disco_f401vc/board.xml @@ -0,0 +1,13 @@ + + + + ../../../../repo.lb + + + + + + + modm:board:disco-f401vc + + diff --git a/src/modm/board/disco_f401vc/module.lb b/src/modm/board/disco_f401vc/module.lb new file mode 100644 index 0000000000..43b103a5e8 --- /dev/null +++ b/src/modm/board/disco_f401vc/module.lb @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2024, Carl Treudler +# +# 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:disco-f401vc" + module.description = """\ +# STM32F401DISCOVERY + +[Discovery kit for STM32F401](https://www.st.com/en/evaluation-tools/32f401cdiscovery.html) +""" + +def prepare(module, options): + if not options[":target"].partname.startswith("stm32f401vct"): + return False + + module.depends( + ":architecture:clock", + ":driver:l3gd20", + ":driver:lsm303a", + ":platform:clock", + ":platform:core", + ":platform:gpio", + ":platform:i2c:1", + ":platform:spi:1", + ":platform:usb:fs") + 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('.') + env.collect(":build:openocd.source", "board/stm32f4discovery.cfg"); From 2381c614a7836f80be4b19e4afefcde3e531543c Mon Sep 17 00:00:00 2001 From: cajt Date: Fri, 5 Jan 2024 19:30:16 +0100 Subject: [PATCH 092/159] [example] Add STM32F401 Discovery examples --- .github/workflows/linux.yml | 2 +- .../accelerometer/main.cpp | 87 ++++++++++++++++++ .../accelerometer/project.xml | 17 ++++ examples/stm32f401_discovery/blink/main.cpp | 40 ++++++++ .../stm32f401_discovery/blink/project.xml | 9 ++ .../stm32f401_discovery/gyroscope/main.cpp | 91 +++++++++++++++++++ .../stm32f401_discovery/gyroscope/project.xml | 14 +++ examples/stm32f401_discovery/uart/main.cpp | 50 ++++++++++ examples/stm32f401_discovery/uart/project.xml | 11 +++ 9 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 examples/stm32f401_discovery/accelerometer/main.cpp create mode 100644 examples/stm32f401_discovery/accelerometer/project.xml create mode 100644 examples/stm32f401_discovery/blink/main.cpp create mode 100644 examples/stm32f401_discovery/blink/project.xml create mode 100644 examples/stm32f401_discovery/gyroscope/main.cpp create mode 100644 examples/stm32f401_discovery/gyroscope/project.xml create mode 100644 examples/stm32f401_discovery/uart/main.cpp create mode 100644 examples/stm32f401_discovery/uart/project.xml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d6e4765bc7..9de02e20ef 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -201,7 +201,7 @@ jobs: - name: Examples STM32F4 Only Discovery Board if: always() run: | - (cd examples && ../tools/scripts/examples_compile.py stm32f4_discovery stm32f429_discovery stm32f469_discovery) + (cd examples && ../tools/scripts/examples_compile.py stm32f4_discovery stm32f429_discovery stm32f469_discovery stm32f401_discovery) stm32f4-examples-2: runs-on: ubuntu-22.04 diff --git a/examples/stm32f401_discovery/accelerometer/main.cpp b/examples/stm32f401_discovery/accelerometer/main.cpp new file mode 100644 index 0000000000..7260dc6124 --- /dev/null +++ b/examples/stm32f401_discovery/accelerometer/main.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015, Kevin Läufer + * Copyright (c) 2015-2018, Niklas Hauser + * Copyright (c) 2024, Carl Treudler + * + * 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 + +using namespace Board; + +// create the data object +Board::lsm3::Accelerometer::Data data; +// and hand it to the sensor driver +Board::lsm3::Accelerometer accelerometer(data); + + +class ReaderThread : public modm::pt::Protothread +{ +public: + bool + update() + { + PT_BEGIN(); + + // initialize with limited range of ±2g + PT_CALL(accelerometer.configure(accelerometer.Scale::G2)); + + while (true) + { + // read out the sensor + PT_CALL(accelerometer.readAcceleration()); + + averageX.update(accelerometer.getData().getX()); + averageY.update(accelerometer.getData().getY()); + + { + bool xs = averageX.getValue() < -0.2f; + bool xn = averageX.getValue() > 0.2f; + + bool xe = averageY.getValue() < -0.2f; + bool xw = averageY.getValue() > 0.2f; + + + LedBlue::set(xs); // South + LedGreen::set(xw); //West + LedOrange::set(xn); // North + LedRed::set(xe); // East + } + + // repeat every 5 ms + timeout.restart(5ms); + PT_WAIT_UNTIL(timeout.isExpired()); + } + + PT_END(); + } + +private: + modm::ShortTimeout timeout; + modm::filter::MovingAverage averageX; + modm::filter::MovingAverage averageY; +}; + +ReaderThread reader; + +int +main() +{ + Board::initialize(); + Board::initializeLsm3(); + + Leds::set(); + modm::delay(42ms); + + modm::fiber::Scheduler::run(); + + return 0; +} diff --git a/examples/stm32f401_discovery/accelerometer/project.xml b/examples/stm32f401_discovery/accelerometer/project.xml new file mode 100644 index 0000000000..bd149df959 --- /dev/null +++ b/examples/stm32f401_discovery/accelerometer/project.xml @@ -0,0 +1,17 @@ + + modm:disco-f401vc + + + + + + modm:driver:lsm303a + modm:math:filter + modm:platform:gpio + modm:platform:i2c + modm:platform:i2c.bitbang + modm:processing:timer + modm:processing:protothread + modm:build:scons + + diff --git a/examples/stm32f401_discovery/blink/main.cpp b/examples/stm32f401_discovery/blink/main.cpp new file mode 100644 index 0000000000..0e0046fa3a --- /dev/null +++ b/examples/stm32f401_discovery/blink/main.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011, Georgi Grinshpun + * Copyright (c) 2011-2012, Fabian Greif + * Copyright (c) 2012, 2014, Sascha Schade + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013, 2015-2017, Niklas Hauser + * Copyright (c) 2024, Carl Treudler + * + * 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; + +// ---------------------------------------------------------------------------- +int +main() +{ + initialize(); + + LedOrange::set(); + LedRed::set(); + + while (true) + { + LedBlue::toggle(); + LedGreen::toggle(); + LedOrange::toggle(); + LedRed::toggle(); + modm::delay(Button::read() ? 250ms : 500ms); + } + + return 0; +} diff --git a/examples/stm32f401_discovery/blink/project.xml b/examples/stm32f401_discovery/blink/project.xml new file mode 100644 index 0000000000..d56c28e21d --- /dev/null +++ b/examples/stm32f401_discovery/blink/project.xml @@ -0,0 +1,9 @@ + + modm:disco-f401vc + + + + + modm:build:scons + + diff --git a/examples/stm32f401_discovery/gyroscope/main.cpp b/examples/stm32f401_discovery/gyroscope/main.cpp new file mode 100644 index 0000000000..233aaa42df --- /dev/null +++ b/examples/stm32f401_discovery/gyroscope/main.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014-2018, Niklas Hauser + * Copyright (c) 2015, Kevin Läufer + * Copyright (c) 2015, Martin Esser + * Copyright (c) 2018, Christopher Durand + * Copyright (c) 2024, Carl Treudler + * + * 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 + +// maps arbitrary gpios to a bit +using LedRing = SoftwareGpioPort< + Board::LedOrange, // 3 + Board::LedRed, // 2 + Board::LedBlue, // 1 + Board::LedGreen // 0 + >; + +// create the data object +Board::l3g::Gyroscope::Data data; +// and hand it to the sensor driver +Board::l3g::Gyroscope gyro(data); + + +class ReaderThread : public modm::pt::Protothread +{ +public: + bool + update() + { + PT_BEGIN(); + + // initialize with limited range of 250 degrees per second + PT_CALL(gyro.configure(gyro.Scale::Dps250)); + + while (true) + { + // read out the sensor + PT_CALL(gyro.readRotation()); + + // update the moving average + averageZ.update(gyro.getData().getZ()); + + { + float value = averageZ.getValue(); + // normalize rotation and scale by 5 leds + uint16_t leds = abs(value / 200 * 5); + leds = (1ul << leds) - 1; + + LedRing::write(leds); + } + + // repeat every 5 ms + timeout.restart(5ms); + PT_WAIT_UNTIL(timeout.isExpired()); + } + + PT_END(); + } + +private: + modm::ShortTimeout timeout; + modm::filter::MovingAverage averageZ; +}; + +ReaderThread reader; + + +int +main() +{ + Board::initialize(); + Board::initializeL3g(); + + while (true) + { + reader.update(); + } + + return 0; +} diff --git a/examples/stm32f401_discovery/gyroscope/project.xml b/examples/stm32f401_discovery/gyroscope/project.xml new file mode 100644 index 0000000000..f175575740 --- /dev/null +++ b/examples/stm32f401_discovery/gyroscope/project.xml @@ -0,0 +1,14 @@ + + modm:disco-f401vc + + + + + modm:driver:l3gd20 + modm:math:filter + modm:platform:spi:1 + modm:processing:timer + modm:processing:protothread + modm:build:scons + + diff --git a/examples/stm32f401_discovery/uart/main.cpp b/examples/stm32f401_discovery/uart/main.cpp new file mode 100644 index 0000000000..02594edcb7 --- /dev/null +++ b/examples/stm32f401_discovery/uart/main.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011, Georgi Grinshpun + * Copyright (c) 2011-2012, Fabian Greif + * Copyright (c) 2012, 2014, Sascha Schade + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013, 2015-2017, Niklas Hauser + * Copyright (c) 2024, Carl Treudler + * + * 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 + +// ---------------------------------------------------------------------------- +/** + * Very basic example of USART usage. + * The ASCII sequence 'A', 'B', 'C', ... , 'Z', 'A', 'B', 'C', ... + * is printed with 9600 baud, 8N1 at pin PA3. + */ +int +main() +{ + Board::initialize(); + + Board::LedRed::set(); + + // Enable USART 2 + Usart2::connect(); + Usart2::initialize(); + + while (true) + { + static uint8_t c = 'A'; + Board::LedRed::toggle(); + Board::LedGreen::toggle(); + Usart2::write(c); + ++c; + if (c > 'Z') { + c = 'A'; + } + modm::delay(500ms); + } + + return 0; +} diff --git a/examples/stm32f401_discovery/uart/project.xml b/examples/stm32f401_discovery/uart/project.xml new file mode 100644 index 0000000000..6f1b628e06 --- /dev/null +++ b/examples/stm32f401_discovery/uart/project.xml @@ -0,0 +1,11 @@ + + modm:disco-f401vc + + + + + modm:platform:gpio + modm:platform:uart:2 + modm:build:scons + + From 26d1edcb0200dba0077921f6b240b8c168408c2a Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 3 Jan 2024 02:11:35 +0100 Subject: [PATCH 093/159] [ext] Update TinyUSB to v0.16.0 --- examples/generic/usb/msc_disk.c | 45 +++++++++++++++++++++++++-------- ext/hathach/module.lb | 2 +- ext/hathach/tinyusb | 2 +- ext/hathach/tusb_config.h.in | 11 +++++--- ext/hathach/uart.hpp | 4 +++ ext/rp/irq.h | 10 ++++---- ext/rp/module.lb | 1 + ext/rp/sync.h | 14 ++++++++++ 8 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 ext/rp/sync.h diff --git a/examples/generic/usb/msc_disk.c b/examples/generic/usb/msc_disk.c index 830bb6e726..c08700a0b1 100644 --- a/examples/generic/usb/msc_disk.c +++ b/examples/generic/usb/msc_disk.c @@ -27,6 +27,9 @@ #if CFG_TUD_MSC +// whether host does safe-eject +static bool ejected = false; + // Some MCU doesn't have enough 8KB SRAM to store the whole disk // We will use Flash as read-only disk with board that has // CFG_EXAMPLE_MSC_READONLY defined @@ -136,7 +139,14 @@ bool tud_msc_test_unit_ready_cb(uint8_t lun) { (void) lun; - return true; // RAM disk is always ready + // RAM disk is ready until ejected + if (ejected) { + // Additional Sense 3A-00 is NOT_FOUND + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + + return true; } // Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size @@ -165,6 +175,7 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo }else { // unload disk storage + ejected = true; } } @@ -177,10 +188,24 @@ int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buff { (void) lun; + // out of ramdisk + if ( lba >= DISK_BLOCK_NUM ) return -1; + uint8_t const* addr = msc_disk[lba] + offset; memcpy(buffer, addr, bufsize); - return bufsize; + return (int32_t) bufsize; +} + +bool tud_msc_is_writable_cb (uint8_t lun) +{ + (void) lun; + +#ifdef CFG_EXAMPLE_MSC_READONLY + return false; +#else + return true; +#endif } // Callback invoked when received WRITE10 command. @@ -189,6 +214,9 @@ int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* { (void) lun; + // out of ramdisk + if ( lba >= DISK_BLOCK_NUM ) return -1; + #ifndef CFG_EXAMPLE_MSC_READONLY uint8_t* addr = msc_disk[lba] + offset; memcpy(addr, buffer, bufsize); @@ -196,7 +224,7 @@ int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* (void) lba; (void) offset; (void) buffer; #endif - return bufsize; + return (int32_t) bufsize; } // Callback invoked when received an SCSI command not in built-in list below @@ -207,18 +235,13 @@ int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, // read10 & write10 has their own callback and MUST not be handled here void const* response = NULL; - uint16_t resplen = 0; + int32_t resplen = 0; // most scsi handled is input bool in_xfer = true; switch (scsi_cmd[0]) { - case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: - // Host is about to read/write etc ... better not to disconnect disk - resplen = 0; - break; - default: // Set Sense = Invalid Command Operation tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); @@ -235,14 +258,14 @@ int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, { if(in_xfer) { - memcpy(buffer, response, resplen); + memcpy(buffer, response, (size_t) resplen); }else { // SCSI output } } - return resplen; + return (int32_t) resplen; } #endif diff --git a/ext/hathach/module.lb b/ext/hathach/module.lb index 2a6048f8aa..9833af0aa1 100644 --- a/ext/hathach/module.lb +++ b/ext/hathach/module.lb @@ -98,7 +98,7 @@ def build(env): # PMA buffer size: 512B or 1024B env.copy("tinyusb/src/portable/st/stm32_fsdev/", "portable/st/stm32_fsdev/") else: - env.copy("tinyusb/src/portable/st/synopsys", "portable/st/synopsys/") + env.copy("tinyusb/src/portable/synopsys/dwc2/", "portable/synopsys/dwc2/") elif target.platform == "sam": if target.family == "g5x": diff --git a/ext/hathach/tinyusb b/ext/hathach/tinyusb index 91853317e1..ed33df8ef6 160000 --- a/ext/hathach/tinyusb +++ b/ext/hathach/tinyusb @@ -1 +1 @@ -Subproject commit 91853317e17c4d8f14263158352e7083b8dad233 +Subproject commit ed33df8ef65f595f85edec1a9cabddd1568fbc7b diff --git a/ext/hathach/tusb_config.h.in b/ext/hathach/tusb_config.h.in index cbb36dc8b3..8c28fed694 100644 --- a/ext/hathach/tusb_config.h.in +++ b/ext/hathach/tusb_config.h.in @@ -34,11 +34,14 @@ // Redirect TinyUSB asserts to use modm_assert #define MODM_ASSERT_1ARGS(_cond) \ - TU_VERIFY_DEFINE(_cond, modm_assert(0, "tu", \ - __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\""), false) + do { \ + modm_assert(_cond, "tu", __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\""); \ + } while(0) #define MODM_ASSERT_2ARGS(_cond, _ret) \ - TU_VERIFY_DEFINE(_cond, modm_assert_continue_fail(0, "tu", \ - __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\""), _ret) + do { \ + if (!modm_assert_continue_fail(_cond, "tu", __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\"")) \ + { return _ret; } \ + } while(0) #define TU_ASSERT(...) _GET_3RD_ARG(__VA_ARGS__, MODM_ASSERT_2ARGS, MODM_ASSERT_1ARGS,UNUSED)(__VA_ARGS__) diff --git a/ext/hathach/uart.hpp b/ext/hathach/uart.hpp index ddb8508def..f737293b40 100644 --- a/ext/hathach/uart.hpp +++ b/ext/hathach/uart.hpp @@ -12,6 +12,8 @@ #pragma once +#if defined(CFG_TUD_CDC) && CFG_TUD_CDC + #include #include "tusb.h" @@ -118,3 +120,5 @@ using UsbUart2 = UsbUart<2>; using UsbUart3 = UsbUart<3>; } // namespace modm::platform + +#endif diff --git a/ext/rp/irq.h b/ext/rp/irq.h index 203e42f59f..dd96da484f 100644 --- a/ext/rp/irq.h +++ b/ext/rp/irq.h @@ -24,7 +24,7 @@ irq_set_enabled(int irqn, bool enable) } static inline void -irq_set_exclusive_handler(int irqn, void (*handler)()) +irq_set_exclusive_handler(int irqn, void (*handler)(void)) { (void) irqn; (void) handler; @@ -32,11 +32,11 @@ irq_set_exclusive_handler(int irqn, void (*handler)()) } static inline void -irq_add_shared_handler(unsigned int irqn, void (*handler)(), uint8_t order_priority) +irq_add_shared_handler(unsigned int irqn, void (*handler)(void), uint8_t order_priority) { - (void) irqn; - (void) handler; - (void) order_priority; + (void) irqn; + (void) handler; + (void) order_priority; } #define PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY 0xff diff --git a/ext/rp/module.lb b/ext/rp/module.lb index 5289bb8cb7..dae5ca965f 100644 --- a/ext/rp/module.lb +++ b/ext/rp/module.lb @@ -47,6 +47,7 @@ def build(env): env.copy("irq.h", "hardware/irq.h") env.copy("timer.h", "hardware/timer.h") env.copy("resets.h", "hardware/resets.h") + env.copy("sync.h", "hardware/sync.h") env.substitutions = { "headers": headers, diff --git a/ext/rp/sync.h b/ext/rp/sync.h new file mode 100644 index 0000000000..bc57b46b3d --- /dev/null +++ b/ext/rp/sync.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024, 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/. + */ +// ---------------------------------------------------------------------------- + +// Stripped down header "hardware/sync.h" for tinyusb + +#define remove_volatile_cast(t, x) ({ __asm volatile ("dmb" ::: "memory"); (t)(x); }) From c507188746aa012cc2fbf84215d64cee2247d381 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 4 Jan 2024 00:59:26 +0100 Subject: [PATCH 094/159] [ext] Refactor TinyUSB module structure and config - Allow running device and host mode on separate ports at the same time. - Allow speed selection of HS port. - Correctly access all host and device class files. --- .../stm32f3_discovery/usb_dfu/project.xml | 2 +- ext/hathach/module.lb | 182 ++++++++++++------ ext/hathach/module.md | 110 ++++++++--- ext/hathach/tusb_descriptors.c.in | 69 ++++++- ext/hathach/tusb_port.cpp.in | 6 +- ext/hathach/uart.hpp | 3 +- src/modm/platform/usb/sam/module.lb | 4 - tools/scripts/docs_modm_io_generator.py | 3 - 8 files changed, 265 insertions(+), 114 deletions(-) diff --git a/examples/stm32f3_discovery/usb_dfu/project.xml b/examples/stm32f3_discovery/usb_dfu/project.xml index f38136e898..9e3d9b6453 100644 --- a/examples/stm32f3_discovery/usb_dfu/project.xml +++ b/examples/stm32f3_discovery/usb_dfu/project.xml @@ -2,7 +2,7 @@ modm:disco-f303vc - + modm:build:scons diff --git a/ext/hathach/module.lb b/ext/hathach/module.lb index 9833af0aa1..077afbfad4 100644 --- a/ext/hathach/module.lb +++ b/ext/hathach/module.lb @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# Copyright (c) 2020, Niklas Hauser +# Copyright (c) 2020, 2024, Niklas Hauser # # This file is part of the modm project. # @@ -13,6 +13,7 @@ from pathlib import Path from collections import defaultdict tusb_config = {} +default_port = 0 # ----------------------------------------------------------------------------- def init(module): @@ -21,14 +22,16 @@ def init(module): def prepare(module, options): device = options[":target"] + has_otg_hs = device.has_driver("usb_otg_hs") + has_otg_fs = device.has_driver("usb_otg_fs") if not (device.has_driver("usb") or - device.has_driver("usb_otg_fs") or - device.has_driver("usb_otg_hs") or + has_otg_fs or + has_otg_hs or device.has_driver("udp") or device.has_driver("uhp")): return False - configs = {"device.cdc", "device.msc", "device.vendor", "device.midi", "device.dfu"} + configs = ["device.cdc", "device.dfu_rt", "device.midi", "device.msc", "device.vendor"] # TODO: Allow all device classes # configs = {"device.{}".format(p.parent.name) # for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")} @@ -39,30 +42,42 @@ def prepare(module, options): module.add_list_option( EnumerationOption(name="config", description="Endpoint Configuration", - enumeration=sorted(configs), + enumeration=configs, dependencies=lambda vs: - [":tinyusb:{}".format(v.replace(".", ":")) for v in vs])) + [":tinyusb:" + v.replace(".", ":") for v in vs])) + # Most devices only have FS=usb, some STM32 have FS/HS=usb_otg_fs/usb_otg_hs # and some STM32 only have HS=usb_otg_hs, so we only define this option if # there is a choice and default this to FS by default. - if device.has_driver("usb_otg_hs") and device.has_driver("usb_otg_fs"): + if has_otg_hs: module.add_option( - EnumerationOption(name="speed", - description="USB Port Speed", - enumeration={"full": "fs", "high": "hs"}, - default="full", - dependencies=lambda s: ":platform:usb:{}s".format(s[0]))) - - module.add_submodule(TinyUsbDeviceModule()) - module.add_submodule(TinyUsbHostModule()) + EnumerationOption(name="max-speed", description="Maximum HS port speed", + enumeration=["full", "high"], default="high")) + # DEPRECATED: 2025q1 + module.add_alias( + Alias(name="speed", description="Renamed for more clarity", + destination=":tinyusb:max-speed")) + # Detect the number of ports and the default port + ports = 2 if (has_otg_hs and has_otg_fs) else 1 + if has_otg_hs and not has_otg_fs: + global default_port + default_port = 1 + # Generate the host and device modules + module.add_submodule(TinyUsbDeviceModule(ports)) + module.add_submodule(TinyUsbHostModule(ports)) module.depends(":cmsis:device", ":architecture:atomic", ":architecture:interrupt", ":platform:usb") return True def validate(env): - if env.has_module(":tinyusb:device") and env.has_module(":tinyusb:host"): - raise ValidateException("TinyUSB won't let you use both Device *and* Host at the same time!") + has_device = env.has_module(":tinyusb:device") + has_host = env.has_module(":tinyusb:host") + if has_device and has_host: + device_port = env.get(":tinyusb:device:port") + host_port = env.get(":tinyusb:host:port") + if device_port == host_port and host_port is not None: + raise ValidateException("You cannot place Device and Host on the same USB port!") def build(env): @@ -90,7 +105,7 @@ def build(env): target = env[":target"].identifier if target.platform == "stm32": - tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_STM32{}".format(target.family.upper()) + tusb_config["CFG_TUSB_MCU"] = f"OPT_MCU_STM32{target.family.upper()}" # TODO: use modm-devices driver type for this fs_dev = (target.family in ["f0", "f3", "l0", "g4"] or (target.family == "f1" and target.name <= "03")) @@ -106,7 +121,7 @@ def build(env): env.copy("tinyusb/src/portable/microchip/samg/", "portable/microchip/samg/") else: series = "e5x" if target.series.startswith("e5") else target.series - tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAM{}".format(series.upper()) + tusb_config["CFG_TUSB_MCU"] = f"OPT_MCU_SAM{series.upper()}" env.copy("tinyusb/src/portable/microchip/samd/", "portable/microchip/samd/") elif target.platform == "rp": @@ -130,21 +145,16 @@ def build(env): tusb_config["CFG_TUSB_DEBUG_PRINTF"] = "tinyusb_debug_printf" # env.collect(":build:cppdefines.debug", "CFG_TUSB_DEBUG=2") - has_device = env.has_module(":tinyusb:device") - has_host = env.has_module(":tinyusb:host") - # On STM32 with both speeds, the option decides, otherwise we must default - # to HS for the few STM32 with *only* usb_otg_hs, all other devices are FS. - speed = env.get("speed", "hs" if env[":target"].has_driver("usb_otg_hs") else "fs") - port = 0 if speed == "fs" else 1 - - tusb_config["CFG_TUSB_RHPORT0_MODE"] = "OPT_MODE_NONE" - tusb_config["CFG_TUSB_RHPORT1_MODE"] = "OPT_MODE_NONE" - mode = None - if has_device: mode = "OPT_MODE_DEVICE"; - if has_host: mode = "OPT_MODE_HOST"; - if mode is not None: - if "hs" in speed: mode = "({} | OPT_MODE_HIGH_SPEED)".format(mode); - tusb_config["CFG_TUSB_RHPORT{}_MODE".format(port)] = mode + ports = {} + global default_port + for mode in ["device", "host"]: + if env.has_module(f":tinyusb:{mode}"): + port = env.get(f":tinyusb:{mode}:port", default_port) + ports[port] = mode[0] + mode = f"OPT_MODE_{mode.upper()}" + if port == 1 and (speed := env.get("max-speed")) is not None: + mode = f"({mode} | OPT_MODE_{speed.upper()}_SPEED)" + tusb_config[f"CFG_TUSB_RHPORT{port}_MODE"] = mode itf_config = env["config"] # Enumerate the configurations @@ -160,7 +170,7 @@ def build(env): endpoints = {} endpoint_counter = 0 for devclass, devitfs in config_enum.items(): - prefix = "{}".format(devclass.upper()) + prefix = devclass.upper() for itf in devitfs: endpoint_counter += 1 itf_prefix = prefix + str(itf) @@ -169,10 +179,10 @@ def build(env): itf_enum.extend([itf_prefix, itf_prefix + "_DATA"]) endpoints[itf_prefix + "_NOTIF"] = hex(0x80 | endpoint_counter) endpoint_counter += 1 - elif devclass in ["msc", "midi", "vendor", "dfu"]: + elif devclass in ["msc", "midi", "vendor", "dfu_rt"]: itf_enum.append(itf_prefix) else: - raise ValidateException("Unknown ITF device class '{}'!".format(devclass)) + raise ValidateException(f"Unknown ITF device class '{devclass}'!") endpoints[itf_prefix + "_OUT"] = hex(endpoint_counter) # SAMG doesn't support using the same EP number for IN and OUT @@ -181,22 +191,26 @@ def build(env): endpoints[itf_prefix + "_IN"] = hex(0x80 | endpoint_counter) if target.platform == "stm32": - irq_data = env.query(":platform:usb:irqs") - irqs = irq_data["port_irqs"][speed] + irqs = [] + for port, uirqs in env.query(":platform:usb:irqs")["port_irqs"].items(): + port = {"fs": 0, "hs": 1}[port] + if port in ports: + irqs.extend([(irq, port, ports[port]) for irq in uirqs]) elif target.platform == "sam" and target.family in ["g5x"]: - irqs = ["UDP"] + irqs = [("UDP", 0, "d")] elif target.platform == "sam" and target.family in ["d5x/e5x"]: - irqs = ["USB_OTHER", "USB_SOF_HSOF", "USB_TRCPT0", "USB_TRCPT1"] + irqs = [("USB_OTHER", 0, "d"), ("USB_SOF_HSOF", 0, "d"), + ("USB_TRCPT0", 0, "d"), ("USB_TRCPT1", 0, "d")] elif target.platform == "rp" and target.family in ["20"]: - irqs = ["USBCTRL_IRQ"] + irqs = [("USBCTRL_IRQ", 0, "d")] else: - irqs = ["USB"] + irqs = [("USB", 0, "d")] env.substitutions = { "target": target, "config": tusb_config, "irqs": irqs, - "port": port, + "ports": ports, "with_debug": env.has_module(":debug"), "with_cdc": env.has_module(":tinyusb:device:cdc"), "itfs": itfs, @@ -207,9 +221,24 @@ def build(env): if len(itf_config): env.template("tusb_descriptors.c.in"); env.template("tusb_port.cpp.in") + # Add support files shared between device and host classes + classes = env.generated_local_files(filterfunc=lambda path: "_device.c" in path or "_host.c" in path) + classes = {(Path(c).parent.name, Path(c).name.rsplit("_", 1)[0]) for c in classes} + # Add support files outside of naming scheme + if any(p == "net" for p, _ in classes): classes.update({("net", "ncm"), ("net", "net_device")}) + if ("dfu", "dfu_rt") in classes: classes.add(("dfu", "dfu")) + # Filter out classes without support files + classes = ((p, n) for p, n in classes if n not in ["bth", "dfu_rt", "vendor", "ecm_rndis", "ecm"]) + # Now copy the shared support file + for parent, name in classes: + env.copy(f"tinyusb/src/class/{parent}/{name}.h", f"class/{parent}/{name}.h") + # ----------------------------------------------------------------------------- class TinyUsbDeviceModule(Module): + def __init__(self, ports): + self.ports = ports + def init(self, module): module.name = ":tinyusb:device" module.description = """ @@ -223,11 +252,21 @@ Configuration options: """ def prepare(self, module, options): - paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")} - for path in paths: - module.add_submodule(TinyUsbClassModule(path, "device")) + classes = {(p.parent.name, p.name.replace("_device.c", "")) + for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.c")} + for parent, name in classes: + module.add_submodule(TinyUsbClassModule(parent, name, "device")) + if self.ports == 2: + module.add_option( + EnumerationOption(name="port", description="USB Port selection", + enumeration={"fs": 0, "hs": 1}, default="fs", + dependencies=lambda s: f":platform:usb:{s[0]}s")) return True + # def validate(self, env): + # if env.has_module(":tinyusb:device:ncm") and env.has_module(":tinyusb:device:ecm_rndis"): + # raise ValidateException("TinyUSB cannot enable both ECM_RNDIS and NCM network drivers!") + def build(self, env): env.outbasepath = "modm/ext/tinyusb" env.copy("tinyusb/src/device/", "device/") @@ -235,8 +274,9 @@ Configuration options: # ----------------------------------------------------------------------------- class TinyUsbHostModule(Module): - def __init__(self): + def __init__(self, ports): self.config = {} + self.ports = ports def init(self, module): module.name = ":tinyusb:host" @@ -257,9 +297,15 @@ Configuration options: options[":target"].has_driver("uhp:samg*")): return False - paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.h")} - for path in paths: - module.add_submodule(TinyUsbClassModule(path, "host")) + classes = {(p.parent.name, p.name.replace("_host.c", "")) + for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.c")} + for parent, name in classes: + module.add_submodule(TinyUsbClassModule(parent, name, "host")) + if self.ports == 2: + module.add_option( + EnumerationOption(name="port", description="USB Port selection", + enumeration={"fs": 0, "hs": 1}, default="hs", + dependencies=lambda s: f":platform:usb:{s[0]}s")) return True def build(self, env): @@ -270,6 +316,9 @@ Configuration options: # ----------------------------------------------------------------------------- # Only contains those configurations that this modules can generate a descriptor for tu_config_descr = {"device": { + "bth": """ +- `CFG_TUD_BTH_ISO_ALT_COUNT` = [undef] modm default: 1 +""", "cdc": """ - `CFG_TUD_CDC_EP_BUFSIZE` = 64/512 (fs/hs) - `CFG_TUD_CDC_RX_BUFSIZE` = [undef] modm default: 512 @@ -291,13 +340,14 @@ tu_config_descr = {"device": { } } class TinyUsbClassModule(Module): - def __init__(self, path, mode): + def __init__(self, parent, name, mode): + self.parent = parent + self.name = name self.mode = mode - self.name = str(path.name) def init(self, module): - module.name = ":tinyusb:{}:{}".format(self.mode, self.name) - descr = "# {} class {}".format(self.mode.capitalize(), self.name.upper()) + module.name = f":tinyusb:{self.mode}:{self.name}" + descr = f"# {self.mode.capitalize()} class {self.name.upper()}" conf = tu_config_descr.get(self.mode, {}).get(self.name, "") if conf: descr += "\n\nConfiguration options:\n" + conf else: descr += "\n\nPlease consult the TinyUSB docs for configuration options.\n" @@ -314,19 +364,25 @@ class TinyUsbClassModule(Module): def build(self, env): env.outbasepath = "modm/ext/tinyusb" - env.copy("tinyusb/src/class/{}".format(self.name), "class/{}".format(self.name), - ignore=env.ignore_files("*_host.*" if "device" in self.mode else "*_device.*")) + for suffix in ["h", "c"]: + # both classes share the `net_device.h` file + if self.name in ["ncm", "ecm_rndis"] and suffix == "h": + continue + file = f"class/{self.parent}/{self.name}_{self.mode}.{suffix}" + env.copy(f"tinyusb/src/{file}", file) global tusb_config - cfg_name = "CFG_TU{}_{}".format(self.mode[0].upper(), self.name.upper()) - if "DFU" in cfg_name: cfg_name += "_RUNTIME"; - tusb_config[cfg_name] = env[":tinyusb:config"].count("{}.{}".format(self.mode, self.name)) - speed = env.get(":tinyusb:speed", 0) + cfg_name = f"CFG_TU{self.mode[0].upper()}_{self.name.upper()}" + if "DFU_RT" in cfg_name: cfg_name = cfg_name.replace("_RT", "_RUNTIME") + tusb_config[cfg_name] = env[":tinyusb:config"].count(f"{self.mode}.{self.name}") + is_hs = env.get(":tinyusb:max-speed", "full") == "high" if self.mode == "device": # These are the defaults that don't crash TinyUSB. if self.name in ["cdc", "midi", "vendor"]: - tusb_config[cfg_name+"_RX_BUFSIZE"] = 512 if speed else 64 - tusb_config[cfg_name+"_TX_BUFSIZE"] = 512 if speed else 64 + tusb_config[cfg_name+"_RX_BUFSIZE"] = 512 if is_hs else 64 + tusb_config[cfg_name+"_TX_BUFSIZE"] = 512 if is_hs else 64 if self.name in ["msc"]: tusb_config[cfg_name+"_EP_BUFSIZE"] = 512 + if self.name in ["bth"]: + tusb_config[cfg_name+"_ISO_ALT_COUNT"] = 1 diff --git a/ext/hathach/module.md b/ext/hathach/module.md index fae3c65d18..2d6eaa69aa 100644 --- a/ext/hathach/module.md +++ b/ext/hathach/module.md @@ -10,6 +10,64 @@ correct interrupt mapping, a serial number based on the UID of the device, as well as remapping the assertions of TinyUSB. +## Full vs High Speed Ports + +Some microcontroller have USB peripherals capable of using external high speed +transceivers via an ULPI interface. TinyUSB can be configured to run device +and host classes on one port or both on separate ports at the same time. +You can configure this via these module options: + +```xml + + +``` + +However, the high speed capable port can also run in full speed mode, in which +case you must configure TinyUSB to *not* use the ULPI interface: + +```xml + +``` + + +## Initializing USB + +The `modm:platform:usb` module provides the correct way of initializing the USB +peripheral, however, you must connect the right signals too: + +```cpp +// USB is timing-sensitive, so prioritize the IRQs accordingly +Usb::initialize(/*priority=*/3); + +// For Device-Only USB implementations, this is enough +Usb::connect(); + +// But for On-The-Go (OTG) USB implementations, you need more: +Usb::connect(); + +// For high speed USB ports, you need to connect all ULPI signals: +UsbHs::connect< + GpioA5::Ulpick, GpioC0::Ulpistp, GpioC2::Ulpidir, GpioH4::Ulpinxt, + GpioA3::Ulpid0, GpioB0::Ulpid1, GpioB1::Ulpid2, GpioB10::Ulpid3, + GpioB11::Ulpid4, GpioB12::Ulpid5, GpioB13::Ulpid6, GpioB5::Ulpid7>(); +``` + +Note that depending on your specific hardware setup, you may need to fiddle +around to find the right VBus sensing mechanism. Please look at the TinyUSB +board definitions and examples for inspiration. + +```cpp +// Enable hardware Vbus sensing on GpioA9 (this can be tricky to get right!) +USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBDEN; +``` + +!!! warning "USB shares resources with CAN" + Note that on STM32F1 and STM32F3 the USB interrupts and RAM are shared with CAN, + thus there are conflicts in IRQ definitions as well as resource limitions in + hardware. On some STM32F3, the USB IRQs can be remapped, this is done + automatically by our port. + + ## Autogeneration of USB Descriptors You can select the device classes you want to use via the `modm:tinyusb:config` @@ -19,7 +77,7 @@ list option: - `device.msc`: Mass Storage class. - `device.midi`: MIDI device. - `device.vendor`: WebUSB device. -- `device.dfu`: DFU (runtime only). +- `device.dfu_rt`: DFU runtime. Note that you can add multiple devices at the same time, as long as there are enough endpoints and USB RAM available: @@ -56,7 +114,8 @@ descriptors: `tusb_desc_device_t` descriptor. - `const uint8_t* tud_descriptor_configuration_cb(uint8_t index)` to replace the endpoint descriptions. -- `const char* string_desc_arr[]` to replace the string descriptors. +- `const char* tud_string_desc_arr[]` to replace the string descriptors. +- `const uint16_t* tud_descriptor_string_cb(uint8_t index, uint16_t langid)` ## Manual USB Descriptors @@ -68,9 +127,17 @@ manually depend on the device classes you want to implement: ```xml modm:tinyusb:device:audio modm:tinyusb:device:bth +modm:tinyusb:device:cdc +modm:tinyusb:device:dfu +modm:tinyusb:device:dfu_rt +modm:tinyusb:device:ecm_rndis modm:tinyusb:device:hid -modm:tinyusb:device:net +modm:tinyusb:device:midi +modm:tinyusb:device:msc +modm:tinyusb:device:ncm modm:tinyusb:device:usbtmc +modm:tinyusb:device:vendor +modm:tinyusb:device:video ``` Some of these classes require a lot of configuration that you must provide via @@ -78,34 +145,19 @@ the `` file. Please consult the TinyUSB documentation and examples for their purpose. -## Initializing USB - -The `modm:platform:usb` module provides the correct way of initializing the USB -peripheral, however, you must connect the right signals too: - -```cpp -// USB is timing-sensitive, so prioritize the IRQs accordingly -Usb::initialize(/*priority=*/3); +## Host classes -// For Device-Only USB implementations, this is enough -Usb::connect(); +To use the host classes you must depend on them manually as modm does not +provide a configuration option for them: -// But for On-The-Go (OTG) USB implementations, you need more: -Usb::connect(); -// Enable hardware Vbus sensing on GpioA9 (this can be tricky to get right!) -USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBDEN; +```xml +modm:tinyusb:host:cdc +modm:tinyusb:host:cdc_rndis +modm:tinyusb:host:hid +modm:tinyusb:host:msc +modm:tinyusb:host:vendor ``` -Note that depending on your specific hardware setup, you may need to fiddle -around to find the right VBus sensing mechanism. Please look at the TinyUSB -board definitions and examples for inspiration. - -!!! warning "USB shares resources with CAN" - Note that on STM32F1 and STM32F3 the USB interrupts and RAM are shared with CAN, - thus there are conflicts in IRQ definitions as well as resource limitions in - hardware. On some STM32F3, the USB IRQs can be remapped, this is done - automatically by our port. - ## Debugging TinyUSB @@ -130,9 +182,9 @@ Assertion 'tu' failed! Abandoning... ``` -To trace the TinyUSB core, you can add `CFG_TUSB_DEBUG=2` to your CPP flags and +To trace the TinyUSB core, you can add `CFG_TUSB_DEBUG=3` to your CPP flags and the output will be forwarded to `MODM_LOG_DEBUG`. ``` -CFG_TUSB_DEBUG=2 +CFG_TUSB_DEBUG=3 ``` diff --git a/ext/hathach/tusb_descriptors.c.in b/ext/hathach/tusb_descriptors.c.in index 6c97a7c377..924b9ef29c 100644 --- a/ext/hathach/tusb_descriptors.c.in +++ b/ext/hathach/tusb_descriptors.c.in @@ -102,7 +102,7 @@ enum #define EPNUM_{{ endpoint }} {{ value }} %% endfor -%% macro itf_descr(hs=False) +%% macro itf_descr(hs) %% set ep_size = 512 if hs else 64 const uint8_t desc_{{"hs" if hs else "fs"}}_configuration[] = { @@ -116,7 +116,7 @@ const uint8_t desc_{{"hs" if hs else "fs"}}_configuration[] = %% elif itf_class in ["MSC", "MIDI", "VENDOR"] // {{loop.index}}st {{itf_class}}: Interface number, string index, EP Out & EP In address, EP size TUD_{{itf_class}}_DESCRIPTOR(ITF_NUM_{{itf}}, {{loop.index0+4}}, EPNUM_{{itf}}_OUT, EPNUM_{{itf}}_IN, {{ep_size}}), - %% elif itf_class in ["DFU"] + %% elif itf_class in ["DFU_RT"] // Interface number, string index, attributes, detach timeout, transfer size */ TUD_DFU_RT_DESCRIPTOR(ITF_NUM_{{itf}}, {{loop.index0+4}}, 0x0d, 1000, 4096), %% endif @@ -124,10 +124,10 @@ const uint8_t desc_{{"hs" if hs else "fs"}}_configuration[] = }; %% endmacro -{{ itf_descr(hs=False) }} +{{ itf_descr(False) }} -%% if port -{{ itf_descr(hs=True) }} +%% if 1 in ports +{{ itf_descr(True) }} %% endif // Invoked when received GET CONFIGURATION DESCRIPTOR @@ -137,13 +137,62 @@ modm_weak const uint8_t* tud_descriptor_configuration_cb(uint8_t index) { (void)index; // for multiple configurations -%% if port +%% if 1 in ports // Although we are highspeed, host may be fullspeed. return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; %% else return desc_fs_configuration; %% endif } + +%% if 1 in ports + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +const tusb_desc_device_qualifier_t desc_device_qualifier = +{ + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x200, + +%% if with_cdc + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +%% else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +%% endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +modm_weak const uint8_t * +tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const *) &desc_device_qualifier; +} + +static uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; +modm_weak const uint8_t * +tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + // if link speed is high return fullspeed config, and vice versa + // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + memcpy(desc_other_speed_config, tud_descriptor_configuration_cb(index), CONFIG_TOTAL_LEN); + desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; + return desc_other_speed_config; +} + +%% endif // //--------------------------------------------------------------------+ // String Descriptors @@ -151,7 +200,7 @@ tud_descriptor_configuration_cb(uint8_t index) // array of pointer to string descriptors modm_weak const char* -string_desc_arr[] = +tud_string_desc_arr[] = { NULL, // 0: Language "TinyUSB", // 1: Manufacturer @@ -167,7 +216,7 @@ static uint16_t _desc_str[33]; // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long // enough for transfer to complete -const uint16_t* +modm_weak const uint16_t* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { (void)langid; @@ -184,10 +233,10 @@ tud_descriptor_string_cb(uint8_t index, uint16_t langid) } else { - if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) + if (!(index < sizeof(tud_string_desc_arr) / sizeof(tud_string_desc_arr[0]))) return NULL; - const char* str = string_desc_arr[index]; + const char* str = tud_string_desc_arr[index]; // Cap at max char chr_count = strlen(str); diff --git a/ext/hathach/tusb_port.cpp.in b/ext/hathach/tusb_port.cpp.in index 29006dfc79..9240369af4 100644 --- a/ext/hathach/tusb_port.cpp.in +++ b/ext/hathach/tusb_port.cpp.in @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, Erik Henriksson - * Copyright (c) 2020, Niklas Hauser + * Copyright (c) 2020, 2024, Niklas Hauser * * This file is part of the modm project. * @@ -15,10 +15,10 @@ #include #include "tusb.h" -%% for irq in irqs | sort +%% for irq, port, mode in irqs | sort MODM_ISR({{irq}}) { - tud_int_handler({{port}}); + tu{{mode}}_int_handler({{port}}); } %% endfor diff --git a/ext/hathach/uart.hpp b/ext/hathach/uart.hpp index f737293b40..e482f617d1 100644 --- a/ext/hathach/uart.hpp +++ b/ext/hathach/uart.hpp @@ -12,10 +12,11 @@ #pragma once +#include "tusb.h" + #if defined(CFG_TUD_CDC) && CFG_TUD_CDC #include -#include "tusb.h" namespace modm::platform { diff --git a/src/modm/platform/usb/sam/module.lb b/src/modm/platform/usb/sam/module.lb index 2c01dc9031..400febf0df 100644 --- a/src/modm/platform/usb/sam/module.lb +++ b/src/modm/platform/usb/sam/module.lb @@ -28,10 +28,6 @@ def prepare(module, options): return True -def validate(env): - if "samg" in str(env[":target"].identifier) and env.has_module(":tinyusb:host"): - raise ValidateException("USB HOST mode is not currently supported on SAMG family") - def build(env): device = env[":target"] properties = device.properties diff --git a/tools/scripts/docs_modm_io_generator.py b/tools/scripts/docs_modm_io_generator.py index fb7266b249..c032500392 100755 --- a/tools/scripts/docs_modm_io_generator.py +++ b/tools/scripts/docs_modm_io_generator.py @@ -197,9 +197,6 @@ def create_target(argument): chosen_board = next((m for m in modules if ":board:" in m), None) modules = [m for m in modules if ":board" not in m or m == chosen_board] - # Remove :tinyusb:host modules, they conflict with :tinyusb:device modules - modules = [m for m in modules if ":tinyusb:host" not in m] - # Remove :architecture modules. Only the :architecture modules for which actual implementations # exist are include as dependencies of the :platform modules. modules = [m for m in modules if ":architecture" not in m] From dd1ad5c842aa140deeb27c27c9d0bc7f60ea212b Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 4 Jan 2024 02:23:10 +0100 Subject: [PATCH 095/159] [stm32] Move peripheral enumeration to :cmsis:device --- ext/st/module.lb | 48 ++++++++++++++++++- .../gpio/stm32 => ext/st}/peripherals.hpp.in | 0 src/modm/platform/clock/stm32/module.lb | 14 +----- src/modm/platform/gpio/stm32/module.lb | 23 +-------- 4 files changed, 50 insertions(+), 35 deletions(-) rename {src/modm/platform/gpio/stm32 => ext/st}/peripherals.hpp.in (100%) diff --git a/ext/st/module.lb b/ext/st/module.lb index af8b2a5908..7ecbccd12a 100644 --- a/ext/st/module.lb +++ b/ext/st/module.lb @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2016, Fabian Greif -# Copyright (c) 2017, Niklas Hauser +# Copyright (c) 2017, 2024, Niklas Hauser # # This file is part of the modm project. # @@ -101,6 +101,7 @@ def common_rcc_map(env): bprops["peripherals"] = peripherals return rcc_map + def common_header_file(env): """ Gives information about the STM32 header files. For example the STM32F469: @@ -145,6 +146,45 @@ def common_header_file(env): bprops["folder"] = folder return headers + +def common_peripherals(env): + """ + All peripherals translated to the modm naming convention. + + :returns: a sorted list of all peripheral names. + """ + def get_driver(s): + name = None + if "driver" in s: name = translate(s["driver"]) + if "instance" in s: name += translate(s["instance"]) + return name + + def translate(s): + return s.replace("_", "").capitalize() + + device = env[":target"] + gpio_driver = device.get_driver("gpio") + # Get all the peripherals from the GPIO remap-able signals + all_peripherals = {get_driver(remap) for remap in gpio_driver.get("remap", [])} + # Get all the peripherals from the normal GPIO signals + all_peripherals.update(get_driver(s) for gpio in gpio_driver["gpio"] for s in gpio.get("signal", [])) + # Get all peripherals from the driver instances + 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: + all_peripherals.update(dname + translate(i) for i in d["instance"]) + else: + all_peripherals.add(dname) + if dname == "Dma" and d["type"] == "stm32-mux": + all_peripherals.add("Dmamux1") + + all_peripherals.discard(None) + all_peripherals = sorted(list(all_peripherals)) + bprops["all_peripherals"] = all_peripherals + return all_peripherals + + # ----------------------------------------------------------------------------- def init(module): module.name = ":cmsis:device" @@ -157,6 +197,8 @@ def prepare(module, options): module.add_query( EnvironmentQuery(name="rcc-map", factory=common_rcc_map)) + module.add_query( + EnvironmentQuery(name="peripherals", factory=common_peripherals)) module.add_query( EnvironmentQuery(name="headers", factory=common_header_file)) @@ -165,6 +207,7 @@ def prepare(module, options): def validate(env): env.query("rcc-map") + env.query("peripherals") def build(env): env.collect(":build:path.include", "modm/ext") @@ -184,3 +227,6 @@ def build(env): }) env.outbasepath = "modm/src/modm/platform" env.template("device.hpp.in") + + env.outbasepath = "modm/src/modm/platform/core" + env.template("peripherals.hpp.in") diff --git a/src/modm/platform/gpio/stm32/peripherals.hpp.in b/ext/st/peripherals.hpp.in similarity index 100% rename from src/modm/platform/gpio/stm32/peripherals.hpp.in rename to ext/st/peripherals.hpp.in diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index f5460754ed..4dab9d1973 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -128,19 +128,7 @@ def build(env): env.template("rcc.cpp.in") env.template("rcc.hpp.in") - all_peripherals = set() - all_drivers = [d for d in device._properties["driver"] if d["name"] not in ["gpio", "core"]] - translate = lambda s: s.replace("_", "").capitalize() - for d in all_drivers: - dname = translate(d["name"]) - if "instance" in d: - all_peripherals.update(dname + translate(i) for i in d["instance"]) - else: - all_peripherals.add(dname) - if dname == "Dma" and d["type"] == "stm32-mux": - all_peripherals.add("Dmamux1") - - all_peripherals = sorted(list(all_peripherals)) + all_peripherals = env.query(":cmsis:device:peripherals") rcc_map = env.query(":cmsis:device:rcc-map") rcc_enable = {} rcc_reset = {} diff --git a/src/modm/platform/gpio/stm32/module.lb b/src/modm/platform/gpio/stm32/module.lb index 60c5dad557..7876cef738 100644 --- a/src/modm/platform/gpio/stm32/module.lb +++ b/src/modm/platform/gpio/stm32/module.lb @@ -30,7 +30,7 @@ def translate(s): return s.replace("_", "").capitalize() def get_driver(s): - name = "None" + name = None if "driver" in s: name = translate(s["driver"]); if "instance" in s: name += translate(s["instance"]); return name @@ -42,6 +42,7 @@ def extract_signals(signals): afs = {} for s in signals: driver = get_driver(s) + if driver is None: continue name = get_name(s) key = driver + name afs[key] = {"driver": driver, "name": name, "af": s["af"]} @@ -238,22 +239,6 @@ def validate(env): "remap_value": remapped_gpios[key] }) - all_peripherals = [s["driver"] for s in all_signals.values()] - # Signals are not enough, since there are peripherals that don't have signals. - # Example: STM32F401RE < 64pins: SPI4 cannot be connected to any pins. - # FIXME: Allow accessing device file more freely - 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: - all_peripherals.extend([dname + translate(i) for i in d["instance"]]) - else: - all_peripherals.append(dname) - if dname == "Dma" and d["type"] == "stm32-mux": - all_peripherals.append("Dmamux1") - if "remap" in driver: - all_peripherals.extend([get_driver(r) for r in driver["remap"]]) - bprops["all_peripherals"] = sorted(list(set(all_peripherals))) bprops["all_signals"] = sorted(list(set(s["name"] for s in all_signals.values()))) bprops["gpios"] = [bprops[g["port"] + g["pin"]] for g in driver["gpio"]] bprops["port_width"] = 16 @@ -285,7 +270,3 @@ def build(env): env.template("../common/connector.hpp.in", "connector.hpp", filters={"formatPeripheral": get_driver, "printSignalMap": print_remap_group_table}) - - # FIXME: Move to modm:platform:core! - env.outbasepath = "modm/src/modm/platform/core" - env.template("peripherals.hpp.in") From 76cbf62d705b587c67014c9704ca8ef82d343b21 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 4 Jan 2024 00:57:45 +0100 Subject: [PATCH 096/159] [stm32] Separate ULPI clock from OTGHS clock To run the OTGHS port in FS mode the ULPI clock must be disabled. --- ext/st/module.lb | 2 ++ src/modm/platform/clock/stm32/module.lb | 9 ++++----- src/modm/platform/clock/stm32/rcc_impl.hpp.in | 16 +++++++--------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ext/st/module.lb b/ext/st/module.lb index 7ecbccd12a..051355c7df 100644 --- a/ext/st/module.lb +++ b/ext/st/module.lb @@ -178,6 +178,8 @@ def common_peripherals(env): all_peripherals.add(dname) if dname == "Dma" and d["type"] == "stm32-mux": all_peripherals.add("Dmamux1") + if dname == "Usbotghs": + all_peripherals.add("Usbotghsulpi") all_peripherals.discard(None) all_peripherals = sorted(list(all_peripherals)) diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 4dab9d1973..65d5cd9024 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -177,11 +177,10 @@ def build(env): # Fix USBOTG OTG if target.family == "u5" and per == "OTG": per = "Usbotgfs" - if "Usbotgfs" in all_peripherals and per.startswith("OTG"): - if per == "OTGH": - per = "OTGHS" + if ("Usbotgfs" in all_peripherals or "Usbotghs" in all_peripherals) and per.startswith("OTG"): + if per == "OTGH": per = "OTGHS" per = "USB"+per - if "Usbotgfs" in all_peripherals and per.startswith("USB"): + if ("Usbotgfs" in all_peripherals or "Usbotghs" in all_peripherals) and per.startswith("USB"): per = per.replace("USB1", "USB").replace("USB2", "USB") if target.family == "l5" and per == "USBFS": per = "Usb" @@ -190,7 +189,7 @@ def build(env): if "EN" in mode: rcc_enable[per.capitalize()] = (nper, mode["EN"]) if "RST" in mode: - rcc_reset[nper] = mode["RST"] + rcc_reset[per.capitalize()] = (nper, mode["RST"]) env.substitutions.update({ "rcc_enable": rcc_enable, diff --git a/src/modm/platform/clock/stm32/rcc_impl.hpp.in b/src/modm/platform/clock/stm32/rcc_impl.hpp.in index 1ed38f1485..f71428ec39 100644 --- a/src/modm/platform/clock/stm32/rcc_impl.hpp.in +++ b/src/modm/platform/clock/stm32/rcc_impl.hpp.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Niklas Hauser + * Copyright (c) 2019, 2021, 2024, Niklas Hauser * * This file is part of the modm project. * @@ -123,13 +123,12 @@ Rcc::enable() __DSB(); %% for peripheral, (st_per, bus) in rcc_enable.items() | sort if constexpr (peripheral == Peripheral::{{ peripheral }}) - if (not Rcc::isEnabled()) { - RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}EN;{% if st_per in rcc_reset %} __DSB(); - RCC->{{rcc_reset[st_per]}} |= RCC_{{rcc_reset[st_per]}}_{{st_per}}RST; __DSB(); - RCC->{{rcc_reset[st_per]}} &= ~RCC_{{rcc_reset[st_per]}}_{{st_per}}RST;{% endif %}{% if peripheral == "Eth" %} __DSB(); + {% if peripheral in rcc_reset %}if (not Rcc::isEnabled()) {% endif %}{ + RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}EN;{% if peripheral in rcc_reset %} __DSB(); + RCC->{{rcc_reset[peripheral][1]}} |= RCC_{{rcc_reset[peripheral][1]}}_{{rcc_reset[peripheral][0]}}RST; __DSB(); + RCC->{{rcc_reset[peripheral][1]}} &= ~RCC_{{rcc_reset[peripheral][1]}}_{{rcc_reset[peripheral][0]}}RST;{% endif %}{% if peripheral == "Eth" %} __DSB(); RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}RXEN; __DSB(); - RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}TXEN;{% elif peripheral == "Usbotghs" %} __DSB(); - RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}ULPIEN;{% endif %} + RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}TXEN;{% endif %} } %% endfor __DSB(); @@ -147,8 +146,7 @@ Rcc::disable() if constexpr (peripheral == Peripheral::{{ peripheral }}) { RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}EN;{% if peripheral == "Eth" %} __DSB(); RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}RXEN; __DSB(); - RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}TXEN;{% elif peripheral == "Usbotghs" %} __DSB(); - RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}ULPIEN;{% endif %} + RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}TXEN;{% endif %} } %% endfor __DSB(); From 1c7af50ea403ce1baccdd9a55e8586308b7a02b0 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 3 Jan 2024 05:08:52 +0100 Subject: [PATCH 097/159] [stm32] Configure GPIO when connecting to USB The HS port can use both the DP and DM or the ULPI signals. All signals *must* be configured for high speed though! --- src/modm/platform/usb/stm32/module.lb | 9 ++- src/modm/platform/usb/stm32/usb.hpp.in | 97 ++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/modm/platform/usb/stm32/module.lb b/src/modm/platform/usb/stm32/module.lb index 6edbdb6d15..b1dbaa8a21 100644 --- a/src/modm/platform/usb/stm32/module.lb +++ b/src/modm/platform/usb/stm32/module.lb @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# Copyright (c) 2020, Niklas Hauser +# Copyright (c) 2020, 2024, Niklas Hauser # # This file is part of the modm project. # @@ -58,6 +58,11 @@ def common_usb_irqs(env): def generate_instance(env, port, otg=False): env.outbasepath = "modm/src/modm/platform/usb" irq_data = env.query(":platform:usb:irqs") + all_signals = env.query(":platform:gpio:all_signals") + ulpi_signals = {"Ulpick", "Ulpistp", "Ulpidir", "Ulpinxt", + "Ulpid0", "Ulpid1", "Ulpid2", "Ulpid3", + "Ulpid4", "Ulpid5", "Ulpid6", "Ulpid7"} + is_ulpi = len(ulpi_signals & set(all_signals)) == len(ulpi_signals) env.substitutions = { "port": port, "peripheral": "Usbotg{}".format(port) if otg else "Usb", @@ -65,6 +70,7 @@ def generate_instance(env, port, otg=False): "is_remap": irq_data["is_remap"], "irqs": irq_data["port_irqs"][port], "target": env[":target"].identifier, + "is_ulpi": port == "hs" and is_ulpi, } if otg: env.template("usb.hpp.in", "usb_{}.hpp".format(port)) @@ -118,6 +124,7 @@ class UsbInstance(Module): module.description = "{} Speed".format("Full" if self.speed == "fs" else "High") def prepare(self, module, options): + module.depends(":platform:gpio") return True def build(self, env): diff --git a/src/modm/platform/usb/stm32/usb.hpp.in b/src/modm/platform/usb/stm32/usb.hpp.in index 0bf2c4df5c..f01e45a2fa 100644 --- a/src/modm/platform/usb/stm32/usb.hpp.in +++ b/src/modm/platform/usb/stm32/usb.hpp.in @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, Erik Henriksson -* Copyright (c) 2020, Niklas Hauser +* Copyright (c) 2020, 2024, Niklas Hauser * * This file is part of the modm project. * @@ -33,6 +33,14 @@ public: #ifdef PWR_CR2_USV PWR->CR2 |= PWR_CR2_USV; #endif +%% elif target.family in ["h5"] +#ifdef PWR_USBSCR_USB33DEN + PWR->USBSCR |= PWR_USBSCR_USB33DEN; +#endif +%% elif target.family in ["h7"] +#ifdef PWR_CR3_USB33DEN + PWR->CR3 |= PWR_CR3_USB33DEN; +#endif %% endif Rcc::enable(); %% if is_remap @@ -48,20 +56,97 @@ public: connect() { using Connector = GpioConnector; -%% if port == "fs" using Dp = typename Connector::template GetSignal< Gpio::Signal::Dp >; using Dm = typename Connector::template GetSignal< Gpio::Signal::Dm >; %% if is_otg using Id = typename Connector::template GetSignal< Gpio::Signal::Id >; %% endif - static_assert(((Connector::template IsValid and Connector::template IsValid) and sizeof...(Signals) >= 2), - "{{ name }}::connect() requires at least one Dp and one Dm signal!"); + static constexpr bool all_usb = + Connector::template IsValid and Connector::template IsValid; +%% if is_ulpi + static constexpr bool any_usb = + Connector::template IsValid or Connector::template IsValid; + + using Ulpick = typename Connector::template GetSignal< Gpio::Signal::Ulpick >; + using Ulpistp = typename Connector::template GetSignal< Gpio::Signal::Ulpistp >; + using Ulpidir = typename Connector::template GetSignal< Gpio::Signal::Ulpidir >; + using Ulpinxt = typename Connector::template GetSignal< Gpio::Signal::Ulpinxt >; + using Ulpid0 = typename Connector::template GetSignal< Gpio::Signal::Ulpid0 >; + using Ulpid1 = typename Connector::template GetSignal< Gpio::Signal::Ulpid1 >; + using Ulpid2 = typename Connector::template GetSignal< Gpio::Signal::Ulpid2 >; + using Ulpid3 = typename Connector::template GetSignal< Gpio::Signal::Ulpid3 >; + using Ulpid4 = typename Connector::template GetSignal< Gpio::Signal::Ulpid4 >; + using Ulpid5 = typename Connector::template GetSignal< Gpio::Signal::Ulpid5 >; + using Ulpid6 = typename Connector::template GetSignal< Gpio::Signal::Ulpid6 >; + using Ulpid7 = typename Connector::template GetSignal< Gpio::Signal::Ulpid7 >; + + static constexpr bool all_ulpi = + Connector::template IsValid< Ulpick > and + Connector::template IsValid< Ulpistp > and + Connector::template IsValid< Ulpidir > and + Connector::template IsValid< Ulpinxt > and + Connector::template IsValid< Ulpid0 > and + Connector::template IsValid< Ulpid1 > and + Connector::template IsValid< Ulpid2 > and + Connector::template IsValid< Ulpid3 > and + Connector::template IsValid< Ulpid4 > and + Connector::template IsValid< Ulpid5 > and + Connector::template IsValid< Ulpid6 > and + Connector::template IsValid< Ulpid7 >; + static constexpr bool any_ulpi = + Connector::template IsValid< Ulpick > or + Connector::template IsValid< Ulpistp > or + Connector::template IsValid< Ulpidir > or + Connector::template IsValid< Ulpinxt > or + Connector::template IsValid< Ulpid0 > or + Connector::template IsValid< Ulpid1 > or + Connector::template IsValid< Ulpid2 > or + Connector::template IsValid< Ulpid3 > or + Connector::template IsValid< Ulpid4 > or + Connector::template IsValid< Ulpid5 > or + Connector::template IsValid< Ulpid6 > or + Connector::template IsValid< Ulpid7 >; + static_assert((any_ulpi xor any_usb) and (all_ulpi or all_usb), + "{{ name }}::connect() requires at least Dp, Dm (+Id) signals OR 12 ULPI signals:\n" + " - CK\n" + " - STP\n" + " - DIR\n" + " - NXT\n" + " - D0\n" + " - D1\n" + " - D2\n" + " - D3\n" + " - D4\n" + " - D5\n" + " - D6\n" + " - D7"); + + if constexpr (all_ulpi and not all_usb) + { + Rcc::enable(); + GpioSet< Ulpick, Ulpistp, Ulpidir, Ulpinxt, Ulpid0, Ulpid1, + Ulpid2, Ulpid3, Ulpid4, Ulpid5, Ulpid6, Ulpid7>::configure( + Gpio::OutputType::PushPull, Gpio::OutputSpeed::High); + } + else if constexpr (not all_ulpi and all_usb) + { + Rcc::disable(); +%% set idt = "\t" +%% else + static_assert(all_usb and sizeof...(Signals) >= 2, + "{{ name }}::connect() requires at least one Dp and one Dm signal!"); +%% set idt = "" +%% endif + {{idt}}GpioSet::configure(Gpio::OutputType::PushPull, Gpio::OutputSpeed::High); %% if is_otg - Id::configure(Gpio::OutputType::OpenDrain, Gpio::OutputSpeed::High); - Id::configure(Gpio::InputType::PullUp); + {{idt}}Id::configure(Gpio::OutputType::OpenDrain, Gpio::OutputSpeed::High); + {{idt}}Id::configure(Gpio::InputType::PullUp); %% endif +%% if is_ulpi + } %% endif + Connector::connect(); } }; From 1f210c15bb6ecaa9f722322b613a93a391e93a65 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 5 Jan 2024 23:56:23 +0100 Subject: [PATCH 098/159] [board] Enable TinyUSB on more STM32 dev boards --- examples/generic/usb/main.cpp | 2 + examples/generic/usb/project.xml | 38 ++++++++---- examples/stm32f429_discovery/blink/main.cpp | 15 ++++- examples/stm32f429_discovery/logger/main.cpp | 54 ----------------- .../stm32f429_discovery/logger/project.xml | 12 ---- src/modm/board/disco_f429zi/board.hpp | 54 ++++++++++++++--- src/modm/board/disco_f429zi/module.lb | 23 ++++++- src/modm/board/disco_f746ng/module.lb | 40 +++++++++++++ src/modm/board/nucleo_f429zi/board.hpp | 16 ++--- src/modm/board/nucleo_h723zg/board.hpp | 60 +++++++++++++++---- src/modm/board/nucleo_h723zg/module.lb | 11 +++- src/modm/board/nucleo_h743zi/board.hpp | 27 +++++---- 12 files changed, 232 insertions(+), 120 deletions(-) delete mode 100644 examples/stm32f429_discovery/logger/main.cpp delete mode 100644 examples/stm32f429_discovery/logger/project.xml diff --git a/examples/generic/usb/main.cpp b/examples/generic/usb/main.cpp index 38d6501c6a..56372dbfa0 100644 --- a/examples/generic/usb/main.cpp +++ b/examples/generic/usb/main.cpp @@ -39,6 +39,8 @@ int main() { Board::initialize(); Board::initializeUsbFs(); + // DISCO-F746NG also has a HS port: + // Board::initializeUsbHs(); tusb_init(); while (true) diff --git a/examples/generic/usb/project.xml b/examples/generic/usb/project.xml index aa57bea6b0..91e2aceba2 100644 --- a/examples/generic/usb/project.xml +++ b/examples/generic/usb/project.xml @@ -1,25 +1,39 @@ modm:blue-pill-f103 - - - - + + + + + + + + + - - - modm:build:scons - modm:tinyusb - modm:processing:timer - modm:io - - + + + + + + + + + modm:build:scons + modm:tinyusb + modm:processing:timer + modm:io + + + + + diff --git a/examples/stm32f429_discovery/blink/main.cpp b/examples/stm32f429_discovery/blink/main.cpp index c7a2e67843..bb109b0972 100644 --- a/examples/stm32f429_discovery/blink/main.cpp +++ b/examples/stm32f429_discovery/blink/main.cpp @@ -20,9 +20,18 @@ main() Board::initialize(); LedRed::set(); - usb::VBus::setOutput(modm::Gpio::Low); + usb::Vbus::setOutput(modm::Gpio::Low); usb::Overcurrent::setOutput(modm::Gpio::Low); + // 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; + + uint32_t counter(0); + while (true) { LedRed::toggle(); @@ -30,10 +39,12 @@ main() modm::delay(Button::read() ? 125ms : 500ms); - usb::VBus::toggle(); + usb::Vbus::toggle(); usb::Overcurrent::toggle(); modm::delay(Button::read() ? 125ms : 500ms); + + MODM_LOG_INFO << "loop: " << counter++ << modm::endl; } return 0; diff --git a/examples/stm32f429_discovery/logger/main.cpp b/examples/stm32f429_discovery/logger/main.cpp deleted file mode 100644 index 64758c7795..0000000000 --- a/examples/stm32f429_discovery/logger/main.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2011, Fabian Greif - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013-2017, Niklas Hauser - * Copyright (c) 2014, Sascha Schade - * - * 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 - -// ---------------------------------------------------------------------------- -// 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< Usart1, modm::IOBuffer::BlockIfFull > 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); - -// ---------------------------------------------------------------------------- -int -main() -{ - Board::initialize(); - - // initialize Uart2 for MODM_LOG_ - Usart1::connect(); - Usart1::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) - { - } - - return 0; -} diff --git a/examples/stm32f429_discovery/logger/project.xml b/examples/stm32f429_discovery/logger/project.xml deleted file mode 100644 index 72e8229fd6..0000000000 --- a/examples/stm32f429_discovery/logger/project.xml +++ /dev/null @@ -1,12 +0,0 @@ - - modm:disco-f429zi - - - - - modm:debug - modm:platform:gpio - modm:platform:uart:1 - modm:build:scons - - diff --git a/src/modm/board/disco_f429zi/board.hpp b/src/modm/board/disco_f429zi/board.hpp index 5f76b349e2..2a859ac41e 100644 --- a/src/modm/board/disco_f429zi/board.hpp +++ b/src/modm/board/disco_f429zi/board.hpp @@ -16,19 +16,23 @@ #include #include +#include using namespace modm::platform; +/// @ingroup modm_board_disco_f429zi +#define MODM_BOARD_HAS_LOGGER + namespace Board { /// @ingroup modm_board_disco_f429zi /// @{ using namespace modm::literals; -/// STM32F429 running at 180MHz from the external 8MHz crystal +/// STM32F429 running at 168MHz from the external 8MHz crystal struct SystemClock { - static constexpr uint32_t Frequency = 180_MHz; + static constexpr uint32_t Frequency = 168_MHz; static constexpr uint32_t Apb1 = Frequency / 4; static constexpr uint32_t Apb2 = Frequency / 2; @@ -74,14 +78,17 @@ struct SystemClock static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { Rcc::enableExternalCrystal(); // 8 MHz const Rcc::PllFactors pllFactors{ - .pllM = 4, // 8MHz / M=4 -> 2MHz - .pllN = 180, // 2MHz * N=180 -> 360MHz - .pllP = 2 // 360MHz / P=2 -> 180MHz = F_cpu + .pllM = 4, // 8MHz / M -> 2MHz + .pllN = 168, // 2MHz * N -> 336MHz + .pllP = 2, // 336MHz / P -> 168MHz = F_cpu + .pllQ = 7 // 336MHz / Q -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); // Required for 180 MHz clock @@ -222,20 +229,37 @@ using Id = GpioOutputB12; // OTG_FS_ID: USB_OTG_HS_ID using Overcurrent = GpioC5; // OTG_FS_OC [OTG_FS_OverCurrent]: GPXTI5 using Power = GpioOutputC4; // OTG_FS_PSO [OTG_FS_PowerSwitchOn] -using VBus = GpioB13; // VBUS_FS: USB_OTG_HS_VBUS -//using Device = UsbFs; +using Vbus = GpioB13; // VBUS_FS: USB_OTG_HS_VBUS + +using Device = UsbHs; +/// @} +} + +namespace stlink +{ +/// @ingroup modm_board_nucleo_h743zi +/// @{ +using Tx = GpioOutputA9; +using Rx = GpioInputA10; +using Uart = Usart1; /// @} } + /// @ingroup modm_board_disco_f429zi /// @{ +using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; + inline void initialize() { SystemClock::enable(); SysTickTimer::initialize(); + stlink::Uart::connect(); + stlink::Uart::initialize(); + LedGreen::setOutput(modm::Gpio::Low); LedRed::setOutput(modm::Gpio::Low); @@ -250,9 +274,23 @@ initializeL3g() l3g::Cs::setOutput(modm::Gpio::High); l3g::SpiMaster::connect(); - l3g::SpiMaster::initialize(); + l3g::SpiMaster::initialize(); l3g::SpiMaster::setDataMode(l3g::SpiMaster::DataMode::Mode3); } + +inline void +initializeUsbFs(uint8_t priority=3) +{ + Rcc::enable(); + usb::Device::initialize(priority); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_HS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_HS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; +} /// @} } diff --git a/src/modm/board/disco_f429zi/module.lb b/src/modm/board/disco_f429zi/module.lb index 969231efc3..25e0b943e0 100644 --- a/src/modm/board/disco_f429zi/module.lb +++ b/src/modm/board/disco_f429zi/module.lb @@ -17,6 +17,22 @@ def init(module): # STM32F429IDISCOVERY [Discovery kit for STM32F429](https://www.st.com/en/evaluation-tools/32f429idiscovery.html) + +## Logging + +To use the logging, you need to close SB11 and SB15 and upgrade the STLINK/V2-B +firmware! See Section 6.3 "Embedded ST-LINK/V2-B" of UM1670. + +## TinyUSB + +To use the USB port, you must configure TinyUSB to use the HS port in FS mode: + +```xml + + + + +``` """ def prepare(module, options): @@ -24,18 +40,21 @@ def prepare(module, options): return False module.depends( + ":debug", ":architecture:clock", ":driver:l3gd20", ":platform:clock", ":platform:core", ":platform:gpio", - ":platform:spi:5") + ":platform:spi:5", + ":platform:uart:1", + ":platform:usb:hs") return True def build(env): env.outbasepath = "modm/src/modm/board" env.substitutions = { - "with_logger": False, + "with_logger": True, "with_assert": env.has_module(":architecture:assert") } env.template("../board.cpp.in", "board.cpp") diff --git a/src/modm/board/disco_f746ng/module.lb b/src/modm/board/disco_f746ng/module.lb index e80e42015e..c8a05a365b 100644 --- a/src/modm/board/disco_f746ng/module.lb +++ b/src/modm/board/disco_f746ng/module.lb @@ -17,6 +17,46 @@ def init(module): # STM32F7DISCOVERY [Discovery kit for STM32F746](https://www.st.com/en/evaluation-tools/32f746gdiscovery.html) + +## TinyUSB + +This board has two USB ports: one with Full Speed support and another with true +High Speed support. By default, TinyUSB runs the device classes on the FS port, +however, you can reassign it to HS via this option: + +```xml + + + + +``` + +Remember to initialize the HS instead of the FS port via the BSP: + +```cpp +Board::initialize(); +Board::initializeUsbHs(); +``` + +Note that can use TinyUSB with both the device and host classes at the same time +if you assign them to different ports: + +```xml +```xml + + + + + +``` + +You must initialize both ports via the BSP: + +```cpp +Board::initialize(); +Board::initializeUsbFs(); +Board::initializeUsbHs(); +``` """ def prepare(module, options): diff --git a/src/modm/board/nucleo_f429zi/board.hpp b/src/modm/board/nucleo_f429zi/board.hpp index 48ee4f59f0..256003a372 100644 --- a/src/modm/board/nucleo_f429zi/board.hpp +++ b/src/modm/board/nucleo_f429zi/board.hpp @@ -145,17 +145,17 @@ using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockI inline void initialize() { - SystemClock::enable(); - SysTickTimer::initialize(); + SystemClock::enable(); + SysTickTimer::initialize(); - stlink::Uart::connect(); - stlink::Uart::initialize(); + stlink::Uart::connect(); + stlink::Uart::initialize(); - LedGreen::setOutput(modm::Gpio::Low); - LedBlue::setOutput(modm::Gpio::Low); - LedRed::setOutput(modm::Gpio::Low); + LedGreen::setOutput(modm::Gpio::Low); + LedBlue::setOutput(modm::Gpio::Low); + LedRed::setOutput(modm::Gpio::Low); - Button::setInput(); + Button::setInput(); } inline void diff --git a/src/modm/board/nucleo_h723zg/board.hpp b/src/modm/board/nucleo_h723zg/board.hpp index ed286d3422..2b28ff010a 100644 --- a/src/modm/board/nucleo_h723zg/board.hpp +++ b/src/modm/board/nucleo_h723zg/board.hpp @@ -27,7 +27,7 @@ namespace Board /// @{ using namespace modm::literals; -/// STM32H723ZG running at 500MHz from PLL clock generated from 8 MHz HSE +/// STM32H723ZG running at 550MHz from PLL clock generated from 8 MHz HSE struct SystemClock { // Max 550MHz @@ -102,7 +102,7 @@ struct SystemClock static constexpr uint32_t Timer23 = Apb1Timer; static constexpr uint32_t Timer24 = Apb1Timer; - static constexpr uint32_t Usb = Ahb1; + static constexpr uint32_t Usb = 48_MHz; // From PLL3Q static bool inline enable() @@ -121,6 +121,16 @@ struct SystemClock .pllR = 2, // 550 MHz / 2 = 275 MHz }; Rcc::enablePll1(Rcc::PllSource::Hse, pllFactors1); + // Use PLL3 for USB 48MHz + const Rcc::PllFactors pllFactors3{ + .range = Rcc::PllInputRange::MHz4_8, + .pllM = 2, // 8MHz / M= 4MHz + .pllN = 60, // 4MHz * N= 240MHz + .pllP = 5, // 240MHz / P= 48MHz + .pllQ = 5, // 240MHz / Q= 48MHz = F_usb + .pllR = 5, // 240MHz / R= 48MHz + }; + Rcc::enablePll3(Rcc::PllSource::ExternalClock, pllFactors3); Rcc::setFlashLatency(); // max. 275MHz @@ -133,6 +143,7 @@ struct SystemClock // update clock frequencies Rcc::updateCoreFrequency(); + Rcc::enableUsbClockSource(Rcc::UsbClockSource::Pll3Q); // switch system clock to pll Rcc::enableSystemClock(Rcc::SystemClockSource::Pll1P); @@ -152,6 +163,22 @@ using LedRed = GpioOutputB14; using Leds = SoftwareGpioPort< LedRed, LedYellow, LedGreen >; /// @} +namespace usb +{ +/// @ingroup modm_board_nucleo_h723zg +/// @{ +using Vbus = GpioA9; +using Id = GpioA10; +using Dm = GpioA11; +using Dp = GpioA12; + +using Overcurrent = GpioInputG7; +using Power = GpioOutputG6; + +using Device = UsbHs; +/// @} +} + namespace stlink { /// @ingroup modm_board_nucleo_h723zg @@ -169,20 +196,31 @@ using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockI inline void initialize() { - SystemClock::enable(); - SysTickTimer::initialize(); + SystemClock::enable(); + SysTickTimer::initialize(); - stlink::Uart::connect(); - stlink::Uart::initialize(); + stlink::Uart::connect(); + stlink::Uart::initialize(); - LedGreen::setOutput(modm::Gpio::Low); - LedYellow::setOutput(modm::Gpio::Low); - LedRed::setOutput(modm::Gpio::Low); + LedGreen::setOutput(modm::Gpio::Low); + LedYellow::setOutput(modm::Gpio::Low); + LedRed::setOutput(modm::Gpio::Low); - Button::setInput(); + Button::setInput(); } -/// @} +inline void +initializeUsbFs(uint8_t priority=3) +{ + usb::Device::initialize(priority); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_HS->GCCFG |= USB_OTG_GCCFG_VBDEN; } /// @} +} + diff --git a/src/modm/board/nucleo_h723zg/module.lb b/src/modm/board/nucleo_h723zg/module.lb index 88ff9d2bf8..d484059a6c 100644 --- a/src/modm/board/nucleo_h723zg/module.lb +++ b/src/modm/board/nucleo_h723zg/module.lb @@ -17,6 +17,14 @@ def init(module): # NUCLEO-H723ZG [Nucleo kit for STM32H723ZG](https://www.st.com/en/evaluation-tools/nucleo-h723zg.html) + +## TinyUSB + +To use the USB port, you must configure TinyUSB to use the HS port in FS mode: + +```xml + +``` """ def prepare(module, options): @@ -29,7 +37,8 @@ def prepare(module, options): ":platform:core", ":platform:gpio", ":platform:clock", - ":platform:uart:3") + ":platform:uart:3", + ":platform:usb:hs") return True diff --git a/src/modm/board/nucleo_h743zi/board.hpp b/src/modm/board/nucleo_h743zi/board.hpp index bf206d9d63..9127839453 100644 --- a/src/modm/board/nucleo_h743zi/board.hpp +++ b/src/modm/board/nucleo_h743zi/board.hpp @@ -148,9 +148,12 @@ using LedGreen = GpioOutputB0; using LedBlue = GpioOutputB7; using LedRed = GpioOutputB14; using Leds = SoftwareGpioPort< LedRed, LedBlue, LedGreen >; +/// @} namespace usb { +/// @ingroup modm_board_nucleo_h743zi +/// @{ using Vbus = GpioA9; using Id = GpioA10; using Dm = GpioA11; @@ -160,41 +163,45 @@ using Overcurrent = GpioInputG7; // OTG_FS_OverCurrent using Power = GpioOutputG6; // OTG_FS_PowerSwitchOn using Device = UsbFs; +/// @} } namespace stlink { +/// @ingroup modm_board_nucleo_h743zi +/// @{ using Tx = GpioOutputD8; using Rx = GpioInputD9; using Uart = Usart3; +/// @} } +/// @ingroup modm_board_nucleo_h743zi +/// @{ using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; inline void initialize() { - SystemClock::enable(); - SysTickTimer::initialize(); + SystemClock::enable(); + SysTickTimer::initialize(); - stlink::Uart::connect(); - stlink::Uart::initialize(); + stlink::Uart::connect(); + stlink::Uart::initialize(); - LedGreen::setOutput(modm::Gpio::Low); - LedBlue::setOutput(modm::Gpio::Low); - LedRed::setOutput(modm::Gpio::Low); + LedGreen::setOutput(modm::Gpio::Low); + LedBlue::setOutput(modm::Gpio::Low); + LedRed::setOutput(modm::Gpio::Low); - Button::setInput(); + Button::setInput(); } -/// FIXME: USB does not work on this board. inline void initializeUsbFs(uint8_t priority=3) { usb::Device::initialize(priority); usb::Device::connect(); - usb::Id::configure(Gpio::InputType::Floating); usb::Overcurrent::setInput(); usb::Vbus::setInput(); From 7de228991ef41b7651bf912d629a3f97aba7168b Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 14 Jan 2024 01:30:46 +0100 Subject: [PATCH 099/159] [docs] Fix links in Windows install instructions --- docs/src/guide/installation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/guide/installation.md b/docs/src/guide/installation.md index d6f937ce80..266c247b9d 100644 --- a/docs/src/guide/installation.md +++ b/docs/src/guide/installation.md @@ -260,7 +260,7 @@ files in the next steps. #### ARM Cortex-M -Install the [pre-built ARM toolchain via the 64-bit installer][toolchain-arm-xpack] +Install the [pre-built ARM toolchain via the 64-bit installer](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads) and make sure you select **Add path to environment variable** at the end! Open a new command prompt to test the compiler: @@ -416,12 +416,12 @@ picocom --baud 115200 --imap lfcrlf --echo /dev/ttyACM0 [openocd-install]: https://github.com/rleh/openocd-build [udev-rules-openocd]: https://github.com/openocd-org/openocd/blob/master/contrib/60-openocd.rules#L84-L99 [usbipd]: https://github.com/dorssel/usbipd-win -[winavr]: https://blog.zakkemble.net/avr-gcc-builds/ +[winavr]: https://github.com/ZakKemble/avr-gcc-build/releases/tag/v12.1.0-1 [windows-store-ubuntu-22-04-1-lts]: https://www.microsoft.com/store/productId/9PN20MSR04DW [wingit]: https://git-scm.com/download/win [winterm]: https://github.com/Microsoft/Terminal [winwsl]: https://docs.microsoft.com/en-us/windows/wsl/about -[wsl2]: https://docs.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2 +[wsl2]: https://docs.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2 [wsl-connect-usb]: https://docs.microsoft.com/en-us/windows/wsl/connect-usb [wsl-install]: https://docs.microsoft.com/en-us/windows/wsl/install [wsl-vscode]: https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-vscode From ae9dca24681d7b8629f2d8012a4fc11eac69a72b Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 14 Jan 2024 01:31:10 +0100 Subject: [PATCH 100/159] [avr] Linker flag --no-warn-rwx-segment not needed on GCC12 --- src/modm/platform/core/avr/module.lb | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modm/platform/core/avr/module.lb b/src/modm/platform/core/avr/module.lb index 8ceea5746d..97c68273a6 100644 --- a/src/modm/platform/core/avr/module.lb +++ b/src/modm/platform/core/avr/module.lb @@ -36,7 +36,6 @@ def build(env): env.outbasepath = "modm/link" env.copy("linkerscript.ld") env.collect(":build:linkflags", "-L{project_source_dir}", - "-Wl,--no-warn-rwx-segment", "-T{}".format(env.relcwdoutpath("modm/link/linkerscript.ld"))) env.substitutions = { From f90d87e6a9b697d40db791e4f5c68eab483666e3 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 14 Jan 2024 15:17:23 +0100 Subject: [PATCH 101/159] [ci] Allow filtering out examples by Python query --- examples/avr/xpcc/receiver/project.xml | 1 + examples/avr/xpcc/sender/project.xml | 1 + tools/scripts/examples_compile.py | 14 +++++++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/avr/xpcc/receiver/project.xml b/examples/avr/xpcc/receiver/project.xml index 8247ec92d7..6cf803d986 100644 --- a/examples/avr/xpcc/receiver/project.xml +++ b/examples/avr/xpcc/receiver/project.xml @@ -1,4 +1,5 @@ + diff --git a/examples/avr/xpcc/sender/project.xml b/examples/avr/xpcc/sender/project.xml index e79e186b86..541f5698bc 100644 --- a/examples/avr/xpcc/sender/project.xml +++ b/examples/avr/xpcc/sender/project.xml @@ -1,4 +1,5 @@ + diff --git a/tools/scripts/examples_compile.py b/tools/scripts/examples_compile.py index e21e7873e7..072bb8f10e 100755 --- a/tools/scripts/examples_compile.py +++ b/tools/scripts/examples_compile.py @@ -35,6 +35,16 @@ def run_command(where, command, all_output=False): output += result.stderr.decode("utf-8", errors="ignore").strip(" \n") return (result.returncode, output) +def enable(projects): + filtered_projects = [] + for project in projects: + if (query := re.search(r"", project.read_text())) is not None and not eval(query[1]): + print(f"Filtering out {project}: {query[1]}") + continue + filtered_projects.append(project) + return filtered_projects + + def generate(project): path = project.parent output = ["=" * 90, "Generating: {}".format(path)] @@ -54,7 +64,7 @@ def build(project): commands.append( ("scons build --cache-show --random", "SCons") ) if ":build:make" in project_cfg and not is_running_on_windows: commands.append( ("make build", "Make") ) - elif ":build:cmake" in project_cfg: + elif ":build:cmake" in project_cfg and not is_running_on_windows: build_dir = re.search(r'name=".+?:build:build.path">(.*?)', project_cfg)[1] cmd = "cmake -E make_directory {}/cmake-build-release; ".format(build_dir) cmd += '(cd {}/cmake-build-release && cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" {}); '.format(build_dir, path.absolute()) @@ -107,6 +117,8 @@ def compile_examples(paths, jobs, split, part): if split > 1: chunk_size = math.ceil(len(projects) / args.split) projects = projects[chunk_size*args.part:min(chunk_size*(args.part+1), len(projects))] + # Filter projects + projects = enable(projects) # first generate all projects with ThreadPool(jobs) as pool: From d1a69844dad85d9c4700d40200f3b0b3a8c06a82 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 14 Jan 2024 16:30:09 +0100 Subject: [PATCH 102/159] [ci] Merge Windows CI workflows and add avr-gcc --- .github/workflows/windows.yml | 126 +++++++++++++++++++++++ .github/workflows/windows_armcortexm.yml | 64 ------------ .github/workflows/windows_hosted.yml | 66 ------------ 3 files changed, 126 insertions(+), 130 deletions(-) create mode 100644 .github/workflows/windows.yml delete mode 100644 .github/workflows/windows_armcortexm.yml delete mode 100644 .github/workflows/windows_hosted.yml diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..7590ae7a67 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,126 @@ +name: Run tests on Windows + +on: [pull_request] + +jobs: + windows_testing: + runs-on: windows-2022 + env: + PYTHONIOENCODING: "utf-8" + + steps: + + # Disabling snake-oil for performance reasons + - name: Disable Windows Defender + run: Set-MpPreference -DisableRealtimeMonitoring $true + + # This doesn't work due to files getting overwritten from inside the zip + # [System.IO.Compression.ZipFile]::ExtractToDirectory("gcc-arm-none-eabi-win64.zip", "C:\") + # And this manual expansion code + # function Unzip($zipfile, $outdir) + # { + # Add-Type -AssemblyName System.IO.Compression.FileSystem + # $archive = [System.IO.Compression.ZipFile]::OpenRead($zipfile) + # foreach ($entry in $archive.Entries) + # { + # $entryTargetFilePath = [System.IO.Path]::Combine($outdir, $entry.FullName) + # $entryDir = [System.IO.Path]::GetDirectoryName($entryTargetFilePath) + # if(!(Test-Path $entryDir )){ + # New-Item -ItemType Directory -Path $entryDir | Out-Null + # } + # if (!$entryTargetFilePath.EndsWith("\") -And !$entryTargetFilePath.EndsWith("/")) { + # [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entryTargetFilePath, $true); + # } + # } + # } + # Unzip -zipfile "gcc-arm-none-eabi-win64.zip" -outdir "C:\" + # is not faster than + # Expand-Archive -Path gcc-arm-none-eabi-win64.zip -DestinationPath C:\ -Force + - name: Download and Unzip GCCs + shell: powershell + run: | + $ProgressPreference = 'SilentlyContinue' + Start-Job { + Set-Location $using:PWD + Add-Type -Assembly "System.IO.Compression.Filesystem" + Invoke-WebRequest -OutFile gcc-win64.zip https://github.com/brechtsanders/winlibs_mingw/releases/download/12.2.0-15.0.6-10.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-12.2.0-mingw-w64msvcrt-10.0.0-r3.zip + [System.IO.Compression.ZipFile]::ExtractToDirectory("gcc-win64.zip", "C:\") + } + Start-Job { + Set-Location $using:PWD + Invoke-WebRequest -OutFile gcc-arm-none-eabi-win64.zip https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi.zip + Expand-Archive -Path gcc-arm-none-eabi-win64.zip -DestinationPath C:\ -Force + } + Start-Job { + Set-Location $using:PWD + Add-Type -Assembly "System.IO.Compression.Filesystem" + Invoke-WebRequest -OutFile gcc-avr-win64.zip https://github.com/ZakKemble/avr-gcc-build/releases/download/v12.1.0-1/avr-gcc-12.1.0-x64-windows.zip + [System.IO.Compression.ZipFile]::ExtractToDirectory("gcc-avr-win64.zip", "C:\") + } + Get-Job | Wait-Job + + - name: Install GCCs + if: always() + shell: powershell + run: | + dir C:\ + dir C:\mingw64 + dir C:\arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi + dir C:\avr-gcc-12.1.0-x64-windows + echo "C:\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo "C:\arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo "C:\avr-gcc-12.1.0-x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + rm gcc-arm-none-eabi-win64.zip + + - name: Install Python + if: always() + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install Python packages + if: always() + run: | + pip install --upgrade --upgrade-strategy=eager modm scons future + + - name: Show Version Information + if: always() + run: | + gcc --version + g++ --version + make --version + arm-none-eabi-g++ --version + avr-g++ --version + lbuild --version + python -c "import os; print(os.cpu_count())" + + - name: Check out repository + if: always() + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Hosted Examples + if: always() + shell: bash + run: | + (cd examples && python ../tools/scripts/examples_compile.py linux/assert linux/block_device/ram linux/build_info linux/git linux/logger linux/printf linux/etl linux/fiber) + + - name: Hosted Unittests + if: always() + shell: bash + run: | + (cd test && make run-hosted-windows) + + - name: Compile STM32 Examples + if: always() + shell: bash + run: | + (cd examples && python ../tools/scripts/examples_compile.py nucleo_f031k6 nucleo_f103rb nucleo_f303re nucleo_f411re nucleo_f746zg) + (cd examples && python ../tools/scripts/examples_compile.py nucleo_g071rb nucleo_l031k6 nucleo_l152re nucleo_l476rg nucleo_g474re) + + - name: Compile AVR Examples + if: always() + shell: bash + run: | + (cd examples && ../tools/scripts/examples_compile.py avr arduino_nano arduino_uno srxe) diff --git a/.github/workflows/windows_armcortexm.yml b/.github/workflows/windows_armcortexm.yml deleted file mode 100644 index 863fdc228f..0000000000 --- a/.github/workflows/windows_armcortexm.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Run ARM Cortex-M tests on Windows - -on: [pull_request] - -jobs: - windows_armcortexm: - runs-on: windows-2022 - env: - PYTHONIOENCODING: "utf-8" - - steps: - - # Disabling snake-oil for performance reasons - - name: Disable Windows Defender - run: Set-MpPreference -DisableRealtimeMonitoring $true - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - - name: Install Python packages - run: | - pip install --user modm scons future - - - name: Download ARM Toolchain - shell: powershell - run: | - $ProgressPreference = 'SilentlyContinue' - Invoke-WebRequest -OutFile gcc-arm-none-eabi-win64.zip https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi.zip - - - name: Install ARM Toolchain - shell: powershell - run: | - Expand-Archive -Path gcc-arm-none-eabi-win64.zip -DestinationPath C:\ -Force - dir C:\arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi - echo "C:\arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-arm-none-eabi\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - rm gcc-arm-none-eabi-win64.zip - - - name: Show lbuild and arm-none-eabi-gcc Version Information - run: | - lbuild --version - arm-none-eabi-g++ --version - python -c "import os; print(os.cpu_count())" - - - name: Check out repository - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - name: Update lbuild - run: | - pip3 install --upgrade --upgrade-strategy=eager modm - - - name: Compile STM32 Examples - shell: bash - run: | - (cd examples && python ../tools/scripts/examples_compile.py nucleo_f031k6 nucleo_f103rb nucleo_f303re nucleo_f411re nucleo_f746zg) - (cd examples && python ../tools/scripts/examples_compile.py nucleo_g071rb nucleo_l031k6 nucleo_l152re nucleo_l476rg nucleo_g474re) - - # - name: Compile AVR Examples - # shell: bash - # run: | - # (cd examples && python ../tools/scripts/examples_compile.py avr) diff --git a/.github/workflows/windows_hosted.yml b/.github/workflows/windows_hosted.yml deleted file mode 100644 index f4dcf46055..0000000000 --- a/.github/workflows/windows_hosted.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Run hosted tests on Windows - -on: [pull_request] - -jobs: - windows_hosted: - runs-on: windows-2022 - env: - PYTHONIOENCODING: "utf-8" - - steps: - - # Disabling snake-oil for performance reasons - - name: Disable Windows Defender - run: Set-MpPreference -DisableRealtimeMonitoring $true - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - - name: Install Python packages - run: | - pip install --user modm scons future - - - name: Download GCC for Windows - run: | - $ProgressPreference = 'SilentlyContinue' - Invoke-WebRequest -OutFile winlibs-gcc.zip https://github.com/brechtsanders/winlibs_mingw/releases/download/12.2.0-15.0.6-10.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-12.2.0-mingw-w64msvcrt-10.0.0-r3.zip - - - name: Unpack GCC for Windows - shell: powershell - run: | - Add-Type -Assembly "System.IO.Compression.Filesystem" - [System.IO.Compression.ZipFile]::ExtractToDirectory("winlibs-gcc.zip", "C:\winlibs-gcc") - dir C:\winlibs-gcc - dir C:\winlibs-gcc\mingw64 - echo "C:\winlibs-gcc\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - - name: Show lbuild and gcc version - run: | - lbuild --version - gcc --version - g++ --version - make --version - python -c "import os; print(os.cpu_count())" - - - name: Check out repository - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - name: Update lbuild - run: | - pip3 install --upgrade --upgrade-strategy=eager modm - - - name: Hosted Examples - shell: bash - run: | - (cd examples && python ../tools/scripts/examples_compile.py linux/assert linux/block_device linux/build_info linux/git linux/logger linux/printf linux/etl linux/fiber) - - - name: Hosted Unittests - if: always() - shell: bash - run: | - (cd test && make run-hosted-windows) From 7318c2885ef60c4dee99212e8bba5c8b1e6d70a5 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 13 Jan 2024 20:00:05 +0100 Subject: [PATCH 103/159] [ext] Catch missing :printf module Accidental use of printf will use the Newlib implementation which uses heap, thus causing an error message about missing :platform:heap module. --- ext/eyalroz/module.lb | 1 - ext/eyalroz/module.md | 21 +++++ ext/eyalroz/no_printf.c.in | 85 +++++++++++++++++++++ src/modm/platform/core/cortex/linker.macros | 33 ++++---- src/modm/platform/core/cortex/module.lb | 12 +++ 5 files changed, 133 insertions(+), 19 deletions(-) create mode 100644 ext/eyalroz/no_printf.c.in diff --git a/ext/eyalroz/module.lb b/ext/eyalroz/module.lb index 6bf72e72b0..8f37ffb357 100644 --- a/ext/eyalroz/module.lb +++ b/ext/eyalroz/module.lb @@ -19,7 +19,6 @@ def prepare(module, options): def build(env): env.collect(":build:path.include", "modm/ext") - env.collect(":build:ccflags", "-fno-builtin-printf") env.outbasepath = "modm/ext/printf" env.copy("printf/src/printf/printf.h", "printf.h") diff --git a/ext/eyalroz/module.md b/ext/eyalroz/module.md index 9beee2b681..45a56eaf52 100644 --- a/ext/eyalroz/module.md +++ b/ext/eyalroz/module.md @@ -44,3 +44,24 @@ extern "C" void putchar_(char c) // MODM_LOG_INFO << c; } ``` + + +## printf is not implemented Error + +This module is not included by default and any attempt to use any of the printf +methods fails with one or multiple linker error messages similiar to this: + +``` +`printf' referenced in section `.text.startup.main' + of main.o: defined in discarded section + `.printf_is_not_implemented!_ + _Please_include_the__modm:printf__module_in_your_project!' + of libmodm.a(no_printf.o) +``` + +This is to prevent you from *accidentally* using the Newlib implementation of +printf, which is very expensive and also dynamically allocated memory. +You can either: + +1. Include this module, which provides a fast printf implementation. +2. Provide your own implementation by strongly linking against printf functions. diff --git a/ext/eyalroz/no_printf.c.in b/ext/eyalroz/no_printf.c.in new file mode 100644 index 0000000000..107245b574 --- /dev/null +++ b/ext/eyalroz/no_printf.c.in @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, 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 +#include +#include + +// ---------------------------------------------------------------------------- +modm_weak modm_section("{{ no_printf_section }}") +int vprintf(const char* format, va_list arg) +{ + (void) format; + (void) arg; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int vsnprintf(char* s, size_t n, const char* format, va_list arg) +{ + (void) s; + (void) n; + (void) format; + (void) arg; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int vsprintf(char* s, const char* format, va_list arg) +{ + (void) s; + (void) format; + (void) arg; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) +{ + (void) out; + (void) extra_arg; + (void) format; + (void) arg; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int printf(const char* format, ...) +{ + (void) format; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int sprintf(char* s, const char* format, ...) +{ + (void) s; + (void) format; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int snprintf(char* s, size_t n, const char* format, ...) +{ + (void) s; + (void) n; + (void) format; + return 0; +} + +modm_weak modm_section("{{ no_printf_section }}") +int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) +{ + (void) out; + (void) extra_arg; + (void) format; + return 0; +} diff --git a/src/modm/platform/core/cortex/linker.macros b/src/modm/platform/core/cortex/linker.macros index 2a719eb890..9dbde54a0d 100644 --- a/src/modm/platform/core/cortex/linker.macros +++ b/src/modm/platform/core/cortex/linker.macros @@ -231,14 +231,8 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }}; . = ALIGN(4); __assertion_table_end = .; } >{{memory}} - - /* We do not call static destructors ever */ - /DISCARD/ : - { - *(.fini_array .fini_array.*) - } - %% if with_cpp_exceptions %# + %% if with_cpp_exceptions /* C++ exception unwind tables */ .exidx : { @@ -257,24 +251,27 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }}; /* required by libc __libc_fini_array, but never called */ _fini = .; - %% else - %# - /* C++ exception unwind tables are discarded */ + %% endif + /DISCARD/ : { + /* We do not call static destructors ever */ + *(.fini_array .fini_array.*) + %% if not with_cpp_exceptions + /* C++ exception unwind tables are discarded */ *(.ARM.extab* .gnu.linkonce.armextab.*) *(.ARM.exidx* .gnu.linkonce.armexidx.*) *(.eh_frame*) - } - %% endif - %% if not with_heap - %# - /* Catch use of dynamic memory without `modm:platform:heap` module. */ - /DISCARD/ : - { + %% endif + %% if not with_heap + /* Catch use of dynamic memory without `modm:platform:heap` module. */ *({{no_heap_section}}) + %% endif + %% if not with_printf + /* Catch use of printf without `modm:printf` module. */ + *({{no_printf_section}}) + %% endif } - %% endif %% endmacro diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb index 75bf17cb34..f00af09659 100644 --- a/src/modm/platform/core/cortex/module.lb +++ b/src/modm/platform/core/cortex/module.lb @@ -127,6 +127,11 @@ def common_memories(env): # See :platform:heap for explanation common_no_heap_section = (".Heap_is_not_implemented!__" "Please_include_the__modm:platform:heap__module_in_your_project!") +# See :printf for explanation +common_no_printf_section = (".printf_is_not_implemented!__" + "Please_include_the__modm:printf__module_in_your_project!") + + def common_linkerscript(env): """ @@ -170,8 +175,10 @@ def common_linkerscript(env): "with_cpp_exceptions": env.get(":stdc++:exceptions", False), "with_heap": env.has_module(":platform:heap"), + "with_printf": env.has_module(":printf"), "with_crashcatcher": env.has_module(":crashcatcher"), "no_heap_section": common_no_heap_section, + "no_printf_section": common_no_printf_section, } properties.update(common_memories(env)) return properties @@ -336,6 +343,11 @@ def build(env): if not env.has_module(":platform:heap"): env.substitutions["no_heap_section"] = common_no_heap_section env.template("../../heap/cortex/no_heap.c.in", "no_heap.c") + # Deprecate printf functions if not implemented! + env.collect(":build:ccflags", "-fno-builtin-printf") + if not env.has_module(":printf"): + env.substitutions["no_printf_section"] = common_no_printf_section + env.template(repopath("ext/eyalroz/no_printf.c.in"), "no_printf.c") if env.has_module(":architecture:atomic"): env.template("atomic_lock_impl.hpp.in") From 2182d3ff4c3cad1fda965bef88671154c8112936 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 17 Jan 2024 22:17:16 +0100 Subject: [PATCH 104/159] [build] Compile with -O3 for hosted targets -Os is not properly supported by LLVM for ARM64 --- tools/build_script_generator/common.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/build_script_generator/common.py b/tools/build_script_generator/common.py index bc0948fa73..432fea35a4 100644 --- a/tools/build_script_generator/common.py +++ b/tools/build_script_generator/common.py @@ -244,9 +244,10 @@ def common_compiler_flags(compiler, target): "-finline-limit=10000", "-funsigned-bitfields", ] - flags["ccflags.release"] = [ - "-Os", - ] + if target.identifier["platform"] in ["hosted"]: + flags["ccflags.release"] = ["-O3"] + else: + flags["ccflags.release"] = ["-Os"] # not a valid profile # flags["ccflags.fast"] = [ # "-O3", From 5db4aa71abf28d08c754bcc63f722e5589ec8716 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 17 Jan 2024 22:18:15 +0100 Subject: [PATCH 105/159] [fiber] Fix alignment of stack underneath promise --- src/modm/processing/fiber/task.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modm/processing/fiber/task.hpp b/src/modm/processing/fiber/task.hpp index 5014c619bf..a0270add92 100644 --- a/src/modm/processing/fiber/task.hpp +++ b/src/modm/processing/fiber/task.hpp @@ -126,13 +126,13 @@ Task::Task(Stack& stack, T&& closure, Start start) } else { - // lambda functions with a closure must be allocated on the stack - constexpr size_t closure_size = sizeof(std::decay_t); + // lambda functions with a closure must be allocated on the stack ALIGNED! + constexpr size_t align_mask = std::max(StackAlignment, alignof(std::decay_t)) - 1u; + constexpr size_t closure_size = (sizeof(std::decay_t) + align_mask) & ~align_mask; static_assert(Size >= closure_size + StackSizeMinimum, - "Stack size must ≥({{min_stack_size}}B + sizeof(closure))!"); + "Stack size must ≥({{min_stack_size}}B + aligned sizeof(closure))!"); // Find a suitable aligned area at the top of stack to allocate the closure - uintptr_t ptr = uintptr_t(stack.memory + stack.words) - closure_size; - ptr &= ~(std::max(sizeof(uintptr_t), alignof(std::decay_t)) - 1u); + const uintptr_t ptr = uintptr_t(stack.memory + stack.words) - closure_size; // construct closure in place ::new ((void*)ptr) std::decay_t{std::forward(closure)}; // Encapsulate the proper ABI function call into a simpler function From f9685317bab11fbcf885a478a05c14c2286bdaf8 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 18 Jan 2024 19:42:22 +0100 Subject: [PATCH 106/159] [devices] Remove RPi target in favor of Linux --- README.md | 7 +- examples/rpi/blinky/main.cpp | 28 ------ examples/rpi/blinky/project.xml | 10 --- src/modm/board/raspberrypi/board.hpp | 30 ------- src/modm/board/raspberrypi/board.xml | 14 --- src/modm/board/raspberrypi/module.lb | 29 ------- .../platform/core/hosted/delay_impl.hpp.in | 2 +- src/modm/platform/gpio/rpi/base.hpp | 52 ----------- src/modm/platform/gpio/rpi/module.lb | 34 -------- src/modm/platform/gpio/rpi/pin.hpp | 67 -------------- src/modm/platform/gpio/rpi/unused.hpp | 87 ------------------- tools/devices/hosted/rpi.xml | 11 --- tools/scripts/docs_modm_io_generator.py | 3 +- tools/scripts/examples_check.py | 2 +- tools/scripts/synchronize_docs.py | 1 - 15 files changed, 6 insertions(+), 371 deletions(-) delete mode 100644 examples/rpi/blinky/main.cpp delete mode 100644 examples/rpi/blinky/project.xml delete mode 100644 src/modm/board/raspberrypi/board.hpp delete mode 100644 src/modm/board/raspberrypi/board.xml delete mode 100644 src/modm/board/raspberrypi/module.lb delete mode 100644 src/modm/platform/gpio/rpi/base.hpp delete mode 100644 src/modm/platform/gpio/rpi/module.lb delete mode 100644 src/modm/platform/gpio/rpi/pin.hpp delete mode 100644 src/modm/platform/gpio/rpi/unused.hpp delete mode 100644 tools/devices/hosted/rpi.xml diff --git a/README.md b/README.md index 98297ef321..5147d6d0c9 100644 --- a/README.md +++ b/README.md @@ -677,21 +677,20 @@ We have out-of-box support for many development boards including documentation. NUCLEO-U575ZI-Q OLIMEXINO-STM32 -Raspberry Pi Raspberry Pi Pico SAMD21-MINI - SAMD21-XPLAINED-PRO + SAME54-XPLAINED-PRO SAME70-XPLAINED SAMG55-XPLAINED-PRO - SAMV71-XPLAINED-ULTRA + Smart Response XE STM32-F4VE STM32F030-DEMO - THINGPLUS-RP2040 + diff --git a/examples/rpi/blinky/main.cpp b/examples/rpi/blinky/main.cpp deleted file mode 100644 index 3a8cf054ac..0000000000 --- a/examples/rpi/blinky/main.cpp +++ /dev/null @@ -1,28 +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/. - */ - -#include -#include - -using namespace Board; - -int main() -{ - Board::initialize(); - GpioPin<0>::setOutput(); - MODM_LOG_INFO << "Blink blink..."; - - for (int i = 0; i < 10; ++i) - { - GpioPin<0>::toggle(); - modm::delay(500ms); - } - return 0; -} diff --git a/examples/rpi/blinky/project.xml b/examples/rpi/blinky/project.xml deleted file mode 100644 index a722c39ba3..0000000000 --- a/examples/rpi/blinky/project.xml +++ /dev/null @@ -1,10 +0,0 @@ - - modm:raspberrypi - - - - - modm:build:scons - modm:build:make - - diff --git a/src/modm/board/raspberrypi/board.hpp b/src/modm/board/raspberrypi/board.hpp deleted file mode 100644 index 65ec809934..0000000000 --- a/src/modm/board/raspberrypi/board.hpp +++ /dev/null @@ -1,30 +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 -#include - -#include - -using namespace modm::platform; - -namespace Board -{ - -inline void -initialize() -{ - wiringPiSetup(); -} - -} // Board namespace diff --git a/src/modm/board/raspberrypi/board.xml b/src/modm/board/raspberrypi/board.xml deleted file mode 100644 index 8b4daa2270..0000000000 --- a/src/modm/board/raspberrypi/board.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - ../../../../repo.lb - - - - - - - - modm:board:raspberrypi - - diff --git a/src/modm/board/raspberrypi/module.lb b/src/modm/board/raspberrypi/module.lb deleted file mode 100644 index 680e2f3332..0000000000 --- a/src/modm/board/raspberrypi/module.lb +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# 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/. -# ----------------------------------------------------------------------------- - -def init(module): - module.name = ":board:raspberrypi" - module.description = """\ -# Raspberry Pi -""" - -def prepare(module, options): - if options[":target"].partname != "hosted-rpi": - return False - - module.depends(":platform:core", ":platform:gpio", ":debug") - return True - -def build(env): - env.outbasepath = "modm/src/modm/board" - env.copy('.') - diff --git a/src/modm/platform/core/hosted/delay_impl.hpp.in b/src/modm/platform/core/hosted/delay_impl.hpp.in index a8d346ac8d..ba2dcf6412 100644 --- a/src/modm/platform/core/hosted/delay_impl.hpp.in +++ b/src/modm/platform/core/hosted/delay_impl.hpp.in @@ -22,7 +22,7 @@ #define MODM_DELAY_NS_IS_ACCURATE 0 -%% if target.family in ["darwin", "linux", "rpi"] +%% if target.family in ["darwin", "linux"] extern "C" { #include } diff --git a/src/modm/platform/gpio/rpi/base.hpp b/src/modm/platform/gpio/rpi/base.hpp deleted file mode 100644 index dad07bc557..0000000000 --- a/src/modm/platform/gpio/rpi/base.hpp +++ /dev/null @@ -1,52 +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 - -namespace modm::platform -{ - -/// @ingroup modm_platform_gpio -enum class -Peripheral -{ - BitBang, - // ... -}; - -/// @ingroup modm_platform_gpio -struct Gpio -{ - /// Each Input Pin can be configured in one of these states. - enum class - InputType : uint8_t - { - Floating = PUD_OFF, - PullUp = PUD_UP, - PullDown = PUD_DOWN, - }; - - enum class - OutputType : uint8_t - { - PushPull ///< push-pull on output - }; - - enum class - Signal - { - BitBang, - }; -}; - -} // namespace modm::platform diff --git a/src/modm/platform/gpio/rpi/module.lb b/src/modm/platform/gpio/rpi/module.lb deleted file mode 100644 index 0bbe2dbd12..0000000000 --- a/src/modm/platform/gpio/rpi/module.lb +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# 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/. -# ----------------------------------------------------------------------------- - - -def init(module): - module.name = ":platform:gpio" - module.description = "Hosted GPIO for Raspberry Pi" - - -def prepare(module, options): - if not options[":target"].has_driver("gpio:wiring-rpi"): - return False - - module.depends(":architecture:gpio") - return True - - -def build(env): - env.substitutions = {"target": env[":target"].identifier} - env.outbasepath = "modm/src/modm/platform/gpio" - - env.collect(":build:library", "wiringPi") - - env.copy(".") - env.copy("../common/inverted.hpp", "inverted.hpp") diff --git a/src/modm/platform/gpio/rpi/pin.hpp b/src/modm/platform/gpio/rpi/pin.hpp deleted file mode 100644 index c6f1190d84..0000000000 --- a/src/modm/platform/gpio/rpi/pin.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021, 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 "base.hpp" - - -namespace modm::platform -{ - -/// @ingroup modm_platform_gpio -template< int Pin > -class GpioPin : public Gpio, public modm::GpioIO -{ - static inline bool output{false}; -public: - using Output = GpioPin; - using Input = GpioPin; - using IO = GpioPin; - using Type = GpioPin; - -public: - inline static void setOutput() { pinMode(Pin, OUTPUT);} - inline static void setOutput(OutputType) { setOutput(); } - inline static void setOutput(bool status) - { - setOutput(); - set(status); - } - - inline static void set() { set(true); } - inline static void reset() { set(false); } - inline static bool isSet() { return output; } - inline static void set(bool status) { digitalWrite(Pin, status); output = status; } - inline static void toggle() - { - if (isSet()) { set(); } - else { reset(); } - } - - inline static void setInput() { pinMode(Pin, INPUT); } - inline static void setInput(Gpio::InputType type) { setInput(); configure(type); } - inline static void configure(Gpio::InputType type) { pullUpDnControl(Pin, int(type)); } - - inline static bool read() { return digitalRead(Pin); } - - inline static modm::Gpio::Direction getDirection() - { return modm::Gpio::Direction::InOut; } - -public: - struct BitBang {} -}; - -} // namespace modm::platform - diff --git a/src/modm/platform/gpio/rpi/unused.hpp b/src/modm/platform/gpio/rpi/unused.hpp deleted file mode 100644 index 37e000bf93..0000000000 --- a/src/modm/platform/gpio/rpi/unused.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2017, Niklas Hauser - * Copyright (c) 2018, Fabian Greif - * - * 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_HOSTED_GPIO_PIN_UNUSED_HPP -#define MODM_HOSTED_GPIO_PIN_UNUSED_HPP - -#include "base.hpp" -#include - -namespace modm -{ - -namespace platform -{ - -/** - * Dummy implementation of an I/O pin. - * - * This class can be used when a pin is not required. All functions - * are dummy functions which do nothing. `read()` will always - * return `false`. - * - * For example when creating a software SPI with the modm::SoftwareSimpleSpi - * class and the return channel (MISO - Master In Slave Out) is not needed, - * a good way is to use this class as a parameter when defining the - * SPI class. - * - * Example: - * @code - * #include - * - * namespace pin - * { - * typedef GpioOutputD7 Clk; - * typedef GpioOutputD5 Mosi; - * } - * - * modm::SoftwareSpiMaster< pin::Clk, pin::Mosi, GpioUnused > Spi; - * - * ... - * Spi::write(0xaa); - * @endcode - * - * @author Fabian Greif - * @author Niklas Hauser - * @ingroup modm_platform_gpio - */ -class GpioUnused : public Gpio, public ::modm::GpioIO -{ -public: - using Output = GpioUnused; - using Input = GpioUnused; - using IO = GpioUnused; - using Type = GpioUnused; - -public: - // GpioOutput - static void setOutput() {} - static void setOutput(bool) {} - static void set() {} - static void set(bool) {} - static void reset() {} - static void toggle() {} - static bool isSet() { return false; } - - // GpioInput - static void setInput() {} - static bool read() { return false; } - - // GpioIO - static Direction getDirection() { return Direction::Special; } -}; - -} // namespace platform - -} // namespace modm - -#endif diff --git a/tools/devices/hosted/rpi.xml b/tools/devices/hosted/rpi.xml deleted file mode 100644 index 1e64700d6f..0000000000 --- a/tools/devices/hosted/rpi.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - {platform}-{family} - - - - - - diff --git a/tools/scripts/docs_modm_io_generator.py b/tools/scripts/docs_modm_io_generator.py index c032500392..55ed7f5b79 100755 --- a/tools/scripts/docs_modm_io_generator.py +++ b/tools/scripts/docs_modm_io_generator.py @@ -36,8 +36,7 @@ def rename_board(name): .replace("BLUE-PILL", "Blue Pill") \ .replace("BLACK-PILL", "Black Pill") \ .replace("ARDUINO-UNO", "Arduino UNO") \ - .replace("ARDUINO-NANO", "Arduino NANO") \ - .replace("RASPBERRYPI", "Raspberry Pi") + .replace("ARDUINO-NANO", "Arduino NANO") sys.path.append(str(repopath("ext/modm-devices"))) from modm_devices.device_identifier import * diff --git a/tools/scripts/examples_check.py b/tools/scripts/examples_check.py index 6c23f07111..3fa837568a 100755 --- a/tools/scripts/examples_check.py +++ b/tools/scripts/examples_check.py @@ -64,7 +64,7 @@ def check_is_part_of_ci(projects): result = 0 # Linux files paths = _get_paths_from_ci([repopath(".github/workflows/linux.yml")]) - paths = folders - paths - {'rpi'} + paths = folders - paths if paths: print("\nLinux CI is missing examples: '{}'" .format("', '".join(sorted(list(paths)))), file=sys.stderr) diff --git a/tools/scripts/synchronize_docs.py b/tools/scripts/synchronize_docs.py index 969051ecd1..3ce9c7e5e0 100755 --- a/tools/scripts/synchronize_docs.py +++ b/tools/scripts/synchronize_docs.py @@ -51,7 +51,6 @@ def name(raw_name): .replace("BLACK-PILL-", "Black Pill ")\ .replace("ARDUINO-UNO", "Arduino UNO")\ .replace("ARDUINO-NANO", "Arduino NANO")\ - .replace("RASPBERRYPI", "Raspberry Pi")\ .replace("RP-PICO", "Raspberry Pi Pico")\ .replace("SRXE", "Smart Response XE")\ .replace("GENERIC", "Generic")\ From 6dff63042ca4e8ec0a848695189e1ffe77e0fcb1 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 17 Jan 2024 22:15:36 +0100 Subject: [PATCH 107/159] [fiber] Add support for ARM64 targets Co-authored-by: Raphael Lehmann --- .../fiber/{context.h.in => context.h} | 2 +- src/modm/processing/fiber/context_arm64.cpp | 200 ++++++++++++++++++ src/modm/processing/fiber/module.lb | 13 +- test/modm/processing/module.lb | 8 +- 4 files changed, 213 insertions(+), 10 deletions(-) rename src/modm/processing/fiber/{context.h.in => context.h} (97%) create mode 100644 src/modm/processing/fiber/context_arm64.cpp diff --git a/src/modm/processing/fiber/context.h.in b/src/modm/processing/fiber/context.h similarity index 97% rename from src/modm/processing/fiber/context.h.in rename to src/modm/processing/fiber/context.h index f532d6be36..d0e78a8fe8 100644 --- a/src/modm/processing/fiber/context.h.in +++ b/src/modm/processing/fiber/context.h @@ -72,7 +72,7 @@ modm_context_start(modm_context_t *to); * to jump from one fiber to the next. */ void -modm_context_jump(modm_context_t *from, modm_context_t *to); +modm_context_jump(modm_context_t *from, modm_context_t *to) asm("modm_context_jump"); /** * Switches control from the fiber context back to the main context. diff --git a/src/modm/processing/fiber/context_arm64.cpp b/src/modm/processing/fiber/context_arm64.cpp new file mode 100644 index 0000000000..a6fc451e9c --- /dev/null +++ b/src/modm/processing/fiber/context_arm64.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2021, 2023, 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 "context.h" +#include + +/* Stack layout (growing downwards): + * + * Permanent Storage: + * Fiber Function + * Fiber Function Argument + * + * Temporary Prepare: + * Entry Function + * + * Register file: + * LR + * FP + * x28 + * x27 + * x26 + * x25 + * x24 + * x23 + * x22 + * x21 + * x20 + * x19 + * d15 + * d14 + * d13 + * d12 + * d11 + * d10 + * d9 + * d8 + */ + +namespace +{ + +constexpr size_t StackWordsReset = 1; +constexpr size_t StackWordsStorage = 2; +constexpr size_t StackWordsRegisters = 20; +constexpr size_t StackWordsAll = StackWordsStorage + StackWordsRegisters; +constexpr size_t StackSizeWord = sizeof(uintptr_t); +constexpr uintptr_t StackWatermark = 0xc0ffee'f00d'facade; + +} + +extern "C" void modm_context_entry() asm("modm_context_entry"); +asm +( + ".globl modm_context_entry \n\t" + "modm_context_entry: \n\t" + "ldr x0, [sp] \n\t" // Load closure data pointer + "ldr x1, [sp, #8] \n\t" // Load closure function + "br x1 \n\t" // Jump to closure function +); + + +void +modm_context_init(modm_context_t *ctx, + uintptr_t *bottom, uintptr_t *top, + uintptr_t fn, uintptr_t fn_arg) +{ + ctx->bottom = bottom; + ctx->top = top; + + ctx->sp = top; + *--ctx->sp = fn; + *--ctx->sp = fn_arg; +} + +void +modm_context_reset(modm_context_t *ctx) +{ + *ctx->bottom = StackWatermark; + + ctx->sp = ctx->top - StackWordsStorage; + *--ctx->sp = (uintptr_t) modm_context_entry; + ctx->sp -= StackWordsRegisters - StackWordsReset; +} + +void +modm_context_watermark(modm_context_t *ctx) +{ + // clear the register file on the stack + for (auto *word = ctx->top - StackWordsAll; + word < ctx->top - StackWordsStorage - StackWordsReset; word++) + *word = 0; + + // then color the whole stack *below* the register file + for (auto *word = ctx->bottom; word < ctx->top - StackWordsAll; word++) + *word = StackWatermark; +} + +size_t +modm_context_stack_usage(const modm_context_t *ctx) +{ + for (auto *word = ctx->bottom; word < ctx->top; word++) + if (StackWatermark != *word) + return (ctx->top - word) * StackSizeWord; + return 0; +} + +bool +modm_context_stack_overflow(const modm_context_t *ctx) +{ + return *ctx->bottom != StackWatermark; +} + +static modm_context_t main_context; + +void +modm_context_start(modm_context_t *to) +{ + modm_context_jump(&main_context, to); +} + +void +modm_context_end() +{ + modm_context_t dummy; + modm_context_jump(&dummy, &main_context); + __builtin_unreachable(); +} + +/* +The assembly code below is adapted from the Boost Context library to work +for Windows, Linux and macOS. +See https://github.com/boostorg/context/tree/develop/src/asm +- Windows: jump_arm64_aapcs_pe_armasm.asm +- Linux: jump_arm64_aapcs_elf_gas.S +- macOS: jump_arm64_aapcs_macho_gas.S + + Copyright Oliver Kowalke 2009. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +asm +( + ".globl modm_context_jump \n\t" + "modm_context_jump: \n\t" + + /* move stack pointer down */ + "sub sp, sp, #0xa0 \n\t" + + /* save d8 - d15 */ + "stp d8, d9, [sp, #0x00] \n\t" + "stp d10, d11, [sp, #0x10] \n\t" + "stp d12, d13, [sp, #0x20] \n\t" + "stp d14, d15, [sp, #0x30] \n\t" + + /* save x19-x30 */ + "stp x19, x20, [sp, #0x40] \n\t" + "stp x21, x22, [sp, #0x50] \n\t" + "stp x23, x24, [sp, #0x60] \n\t" + "stp x25, x26, [sp, #0x70] \n\t" + "stp x27, x28, [sp, #0x80] \n\t" + "stp fp, lr, [sp, #0x90] \n\t" + + /* Store the SP in from->sp */ + "mov x19, sp \n\t" + "str x19, [x0] \n\t" + + /* Restore SP from to->sp */ + "ldr x19, [x1] \n\t" + "mov sp, x19 \n\t" + + /* load d8 - d15 */ + "ldp d8, d9, [sp, #0x00] \n\t" + "ldp d10, d11, [sp, #0x10] \n\t" + "ldp d12, d13, [sp, #0x20] \n\t" + "ldp d14, d15, [sp, #0x30] \n\t" + + /* load x19-x30 */ + "ldp x19, x20, [sp, #0x40] \n\t" + "ldp x21, x22, [sp, #0x50] \n\t" + "ldp x23, x24, [sp, #0x60] \n\t" + "ldp x25, x26, [sp, #0x70] \n\t" + "ldp x27, x28, [sp, #0x80] \n\t" + "ldp fp, lr, [sp, #0x90] \n\t" + + /* restore stack from GP + FPU */ + "add sp, sp, #0xa0 \n\t" + + "ret \n\t" +); diff --git a/src/modm/processing/fiber/module.lb b/src/modm/processing/fiber/module.lb index 22e09dbe3f..9289a2608f 100644 --- a/src/modm/processing/fiber/module.lb +++ b/src/modm/processing/fiber/module.lb @@ -26,8 +26,9 @@ def prepare(module, options): module.add_query( EnvironmentQuery(name="__enabled", factory=is_enabled)) - # No ARM64 support yet! - return "arm64" not in options[":target"].get_driver("core")["type"] + core = options[":target"].get_driver("core")["type"] + return (core.startswith("cortex-m") or core.startswith("avr") or + "x86_64" in core or "arm64" in core) def build(env): @@ -40,6 +41,7 @@ def build(env): "is_cm0": core.startswith("cortex-m0"), "is_avr": core.startswith("avr"), "is_windows": env[":target"].identifier.family == "windows", + "is_darwin": env[":target"].identifier.family == "darwin", "core": core, "with_fpu": with_fpu, "target": env[":target"].identifier, @@ -64,7 +66,12 @@ def build(env): env.substitutions["default_stack_size"] = 2**20 # 1MB env.template("context_x86_64.cpp.in") - env.template("context.h.in") + elif "arm64" in core: + env.substitutions["stack_minimum"] = (20 + 2) * 8 + env.substitutions["default_stack_size"] = 2**20 # 1MB + env.copy("context_arm64.cpp") + + env.copy("context.h") env.template("stack.hpp.in") env.template("scheduler.hpp.in") env.copy("task.hpp") diff --git a/test/modm/processing/module.lb b/test/modm/processing/module.lb index ecd2f590a2..126784da99 100644 --- a/test/modm/processing/module.lb +++ b/test/modm/processing/module.lb @@ -21,19 +21,15 @@ def prepare(module, options): "modm:architecture", "modm:math:utils", "modm:math:filter", + "modm:processing:fiber", "modm:processing:protothread", "modm:processing:resumable", "modm:processing:timer", "modm:processing:scheduler", ":mock:clock") - if "arm64" not in options[":target"].get_driver("core")["type"]: - module.depends("modm:processing:fiber") return True def build(env): env.outbasepath = "modm-test/src/modm-test/processing" - if "arm64" in env[":target"].get_driver("core")["type"]: - env.copy('.', ignore=env.ignore_paths("fiber")) - else: - env.copy('.') + env.copy('.') From c5dc4b11df2453554c3378cd82d7453f35b4e706 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 20 Jan 2024 21:22:30 +0100 Subject: [PATCH 108/159] [devices] Add ARM64 version of hosted targets --- test/Makefile | 4 ++++ tools/devices/hosted/darwin-arm64.xml | 13 +++++++++++++ .../hosted/{darwin.xml => darwin-x86_64.xml} | 2 +- tools/devices/hosted/linux-arm64.xml | 14 ++++++++++++++ .../devices/hosted/{linux.xml => linux-x86_64.xml} | 2 +- tools/devices/hosted/windows.xml | 2 +- tools/scripts/examples_compile.py | 2 ++ 7 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 tools/devices/hosted/darwin-arm64.xml rename tools/devices/hosted/{darwin.xml => darwin-x86_64.xml} (86%) create mode 100644 tools/devices/hosted/linux-arm64.xml rename tools/devices/hosted/{linux.xml => linux-x86_64.xml} (88%) diff --git a/test/Makefile b/test/Makefile index dbb55d7b99..5d41f9d46b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -27,8 +27,12 @@ endef run-hosted-linux: $(call compile-test,hosted,run,-D":target=hosted-linux") +run-hosted-linux-arm64: + $(call compile-test,hosted,run,-D":target=hosted-linux-arm64") run-hosted-darwin: $(call compile-test,hosted,run,-D":target=hosted-darwin") +run-hosted-darwin-arm64: + $(call compile-test,hosted,run,-D":target=hosted-darwin-arm64") run-hosted-windows: $(call compile-test,hosted,run,-D":target=hosted-windows") diff --git a/tools/devices/hosted/darwin-arm64.xml b/tools/devices/hosted/darwin-arm64.xml new file mode 100644 index 0000000000..5d2d79aa7f --- /dev/null +++ b/tools/devices/hosted/darwin-arm64.xml @@ -0,0 +1,13 @@ + + + + + {platform}-{family}-{arch} + + + + + + + + diff --git a/tools/devices/hosted/darwin.xml b/tools/devices/hosted/darwin-x86_64.xml similarity index 86% rename from tools/devices/hosted/darwin.xml rename to tools/devices/hosted/darwin-x86_64.xml index 7ac1b5b7a9..b2db0d85d6 100644 --- a/tools/devices/hosted/darwin.xml +++ b/tools/devices/hosted/darwin-x86_64.xml @@ -1,7 +1,7 @@ - + {platform}-{family} diff --git a/tools/devices/hosted/linux-arm64.xml b/tools/devices/hosted/linux-arm64.xml new file mode 100644 index 0000000000..50ec66b0af --- /dev/null +++ b/tools/devices/hosted/linux-arm64.xml @@ -0,0 +1,14 @@ + + + + + {platform}-{family}-{arch} + + + + + + + + + diff --git a/tools/devices/hosted/linux.xml b/tools/devices/hosted/linux-x86_64.xml similarity index 88% rename from tools/devices/hosted/linux.xml rename to tools/devices/hosted/linux-x86_64.xml index d0e5221a72..8bb6e90bc3 100644 --- a/tools/devices/hosted/linux.xml +++ b/tools/devices/hosted/linux-x86_64.xml @@ -1,7 +1,7 @@ - + {platform}-{family} diff --git a/tools/devices/hosted/windows.xml b/tools/devices/hosted/windows.xml index 79c2c0c27b..06eb4450e9 100644 --- a/tools/devices/hosted/windows.xml +++ b/tools/devices/hosted/windows.xml @@ -1,7 +1,7 @@ - + {platform}-{family} diff --git a/tools/scripts/examples_compile.py b/tools/scripts/examples_compile.py index 072bb8f10e..455ea55245 100755 --- a/tools/scripts/examples_compile.py +++ b/tools/scripts/examples_compile.py @@ -20,6 +20,7 @@ os.getenv("TRAVIS") is not None or os.getenv("GITHUB_ACTIONS") is not None) is_running_on_windows = "Windows" in platform.platform() +is_running_on_arm64 = "arm64" in platform.machine() build_dir = (Path(os.path.abspath(__file__)).parents[2] / "build") cache_dir = build_dir / "cache" global_options = {} @@ -52,6 +53,7 @@ def generate(project): # Compile Linux examples under macOS with hosted-darwin target if "hosted-linux" in project.read_text(): options += " -D:target=hosted-{}".format(platform.system().lower()) + if is_running_on_arm64: options += "-arm64" rc, ro = run_command(path, "lbuild {} build".format(options)) print("\n".join(output + [ro])) return None if rc else project From 623a13bb6c6435911b7b45b19c488d751e65a985 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 20 Jan 2024 22:50:30 +0100 Subject: [PATCH 109/159] [scons] Hardcode gcc-12 compiler suffix on macOS --- tools/build_script_generator/scons/resources/SConscript.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build_script_generator/scons/resources/SConscript.in b/tools/build_script_generator/scons/resources/SConscript.in index 3381e6b540..74e4f04e0a 100644 --- a/tools/build_script_generator/scons/resources/SConscript.in +++ b/tools/build_script_generator/scons/resources/SConscript.in @@ -23,8 +23,8 @@ env["COMPILERPREFIX"] = "avr-" env["COMPILERPREFIX"] = "arm-none-eabi-" %% endif %% if family == "darwin" -# Using homebrew gcc on macOS instead of clang -env["COMPILERSUFFIX"] = env.Detect(["gcc-12", "gcc-11", "gcc-10"])[3:] +# Using homebrew gcc-12 on macOS instead of clang +env["COMPILERSUFFIX"] = "-12" %% endif %% endif From c26ab0015b04c8063fe0ffc61bce1d45225e5902 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 21 Sep 2023 02:17:44 +0200 Subject: [PATCH 110/159] [stm32] Add H7 BDMA driver --- src/modm/platform/dma/stm32-bdma/bdma.cpp.in | 34 ++ src/modm/platform/dma/stm32-bdma/bdma.hpp.in | 441 ++++++++++++++++++ .../platform/dma/stm32-bdma/bdma_base.hpp.in | 162 +++++++ src/modm/platform/dma/stm32-bdma/bdma_hal.hpp | 187 ++++++++ src/modm/platform/dma/stm32-bdma/module.lb | 112 +++++ tools/scripts/generate_hal_matrix.py | 2 +- 6 files changed, 937 insertions(+), 1 deletion(-) create mode 100644 src/modm/platform/dma/stm32-bdma/bdma.cpp.in create mode 100644 src/modm/platform/dma/stm32-bdma/bdma.hpp.in create mode 100644 src/modm/platform/dma/stm32-bdma/bdma_base.hpp.in create mode 100644 src/modm/platform/dma/stm32-bdma/bdma_hal.hpp create mode 100644 src/modm/platform/dma/stm32-bdma/module.lb diff --git a/src/modm/platform/dma/stm32-bdma/bdma.cpp.in b/src/modm/platform/dma/stm32-bdma/bdma.cpp.in new file mode 100644 index 0000000000..a3bb63fbbb --- /dev/null +++ b/src/modm/platform/dma/stm32-bdma/bdma.cpp.in @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, Christopher Durand + * + * 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/. + */ +// ---------------------------------------------------------------------------- + +%% if instance == "" +#include "bdma.hpp" +%% else +#include "bdma_{{ instance }}.hpp" +%% endif + +%% if instance == "1" +MODM_ISR(BDMA1) +{ + using modm::platform::Bdma1; +%% for channel in range(0, channel_count) + Bdma1::Channel{{ channel }}::interruptHandler(); +%% endfor +} +%% else +%% for channel in range(0, channel_count) +MODM_ISR(BDMA{{ instance }}_Channel{{ channel }}) +{ + using modm::platform::Bdma{{ instance }}; + Bdma{{ instance }}::Channel{{ channel }}::interruptHandler(); +} +%% endfor +%% endif diff --git a/src/modm/platform/dma/stm32-bdma/bdma.hpp.in b/src/modm/platform/dma/stm32-bdma/bdma.hpp.in new file mode 100644 index 0000000000..1706365ed9 --- /dev/null +++ b/src/modm/platform/dma/stm32-bdma/bdma.hpp.in @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2014, Kevin Läufer + * Copyright (c) 2014-2017, Niklas Hauser + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2021-2023, Christopher Durand + * + * 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_STM32_BDMA_HPP +#define MODM_STM32_BDMA_HPP + +#include +#include +#include +#include +#include "../device.hpp" +#include "bdma_hal.hpp" + +namespace modm::platform +{ + +%% if instance in ["", "2"] +/// @cond +namespace bdma +{ + template + struct RequestMapping; +} +/// @endcond +%% endif + +/** + * Basic DMA controller {{ instance }} + * + * @ingroup modm_platform_dma + */ +class Bdma{{ instance }} : public BdmaBase +{ +public: + static void + enable() + { + Rcc::enable(); + } + + static void + disable() + { + Rcc::disable(); + } + + template + class Channel : public BdmaBase + { + static_assert(int(Id) >= 0 and int(Id) < {{ channel_count }}, "invalid channel"); + + static constexpr uint32_t ChannelBase = BDMA{{ instance }}_Channel0_BASE + + uint32_t(Id) * (BDMA{{ instance }}_Channel1_BASE - BDMA{{ instance }}_Channel0_BASE); + + using Hal = BdmaChannelHal; + + public: + /** + * Configure the DMA channel + * + * Stops the DMA channel and writes the new values to its control register. + * + * @param[in] direction Direction of the DMA channel + * @param[in] memoryDataSize Size of data in memory (byte, halfword, word) + * @param[in] peripheralDataSize Size of data in peripheral (byte, halfword, word) + * @param[in] memoryIncrement Defines whether the memory address is incremented + * after a transfer completed + * @param[in] peripheralIncrement Defines whether the peripheral address is + * incremented after a transfer completed + * @param[in] priority Priority of the DMA channel + * @param[in] circularMode Transfer data in circular mode? + */ + static void + configure(DataTransferDirection direction, MemoryDataSize memoryDataSize, + PeripheralDataSize peripheralDataSize, + MemoryIncrementMode memoryIncrement, + PeripheralIncrementMode peripheralIncrement, + Priority priority = Priority::Medium, + CircularMode circularMode = CircularMode::Disabled) + { + Hal::configure(direction, memoryDataSize, peripheralDataSize, + memoryIncrement, peripheralIncrement, priority, circularMode); + } + + /** + * Start the transfer of the DMA channel and clear all interrupt flags. + */ + static void + start() + { + clearInterruptFlags(); + Hal::start(); + } + + /** + * Stop a DMA channel transfer + */ + static void + stop() + { + Hal::stop(); + } + + /** + * Get the direction of the data transfer + */ + static DataTransferDirection + getDataTransferDirection() + { + return Hal::getDataTransferDirection(); + } + + /** + * Set the memory 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 + setMemoryAddress(uintptr_t address) + { + Hal::setMemoryAddress(address); + } + /** + * Set the peripheral 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 + setPeripheralAddress(uintptr_t address) + { + Hal::setPeripheralAddress(address); + } + + /** + * Enable/disable memory increment + * + * When enabled, the memory 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 + setMemoryIncrementMode(bool increment) + { + Hal::setMemoryIncrementMode(increment); + } + /** + * Enable/disable peripheral increment + * + * When enabled, the peripheral 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 + setPeripheralIncrementMode(bool increment) + { + Hal::setPeripheralIncrementMode(increment); + } + + /** + * Set the length of data to be transfered + */ + static void + setDataLength(std::size_t length) + { + Hal::setDataLength(length); + } + + /** + * Set the IRQ handler for transfer errors + * + * The handler will be called from the channels IRQ handler function + * when the IRQ status indicates an error occured. + */ + static void + setTransferErrorIrqHandler(IrqHandler irqHandler) + { + transferError = irqHandler; + } + /** + * Set the IRQ handler for half transfer complete + * + * Called by the channels IRQ handler when the transfer is half complete. + */ + static void + setHalfTransferCompleteIrqHandler(IrqHandler irqHandler) + { + halfTransferComplete = irqHandler; + } + /** + * Set the IRQ handler for transfer complete + * + * Called by the channels IRQ handler when the transfer is complete. + */ + static void + setTransferCompleteIrqHandler(IrqHandler irqHandler) + { + transferComplete = irqHandler; + } + +%% if instance in ["", "2"] + /** + * Set the peripheral that operates the channel + */ + template + static void + setPeripheralRequest() + { + constexpr auto muxChannel = std::find_if(muxChannels.begin(), muxChannels.end(), [](MuxChannel ch) { +%% if instance == "" + return ch.dmaChannel == static_cast(Id); +%% else + return (ch.dmaInstance == {{ instance }}) && (ch.dmaChannel == uint32_t(Id)); +%% endif + })->muxChannel; + auto* channel = DMAMUX2_Channel0 + muxChannel; + channel->CCR = (channel->CCR & ~DMAMUX_CxCR_DMAREQ_ID) | uint32_t(dmaRequest); + } +%% endif + + /** + * IRQ handler of the DMA channel + * + * Reads the IRQ status and checks for error or transfer complete. In case + * of error the DMA channel will be disabled. + */ + modm_always_inline static void + interruptHandler() + { + const auto flags = getInterruptFlags(); + if (flags & InterruptFlags::Error) { + disable(); + if (transferError) + transferError(); + } + if (halfTransferComplete and (flags & InterruptFlags::HalfTransferComplete)) { + halfTransferComplete(); + } + if (transferComplete and (flags & InterruptFlags::TransferComplete)) { + transferComplete(); + } + + // only clear flags that have been handled + // otherwise interrupts that occur while inside the handler could be discarded + clearInterruptFlags(flags & ~InterruptFlags::Global); + } + + /** + * Read channel status flags when channel interrupts are disabled. + * This function is useful to query the transfer state when the use of + * the channel interrupt is not required for the application. + * + * @warning Flags are automatically cleared in the ISR if the channel + * interrupt is enabled or when start() is called. + */ + static InterruptFlags_t + getInterruptFlags() + { + const auto globalFlags = BDMA{{ instance }}->ISR; + const auto mask = static_cast(InterruptFlags::All); + const auto shift = static_cast(Id) * 4; + const auto channelFlags = static_cast((globalFlags >> shift) & mask); + return InterruptFlags_t{channelFlags}; + } + + /** + * Clear channel interrupt flags. + * Use only when the channel interrupt is disabled. + * + * @warning Flags are automatically cleared in the ISR if the channel + * interrupt is enabled or when start() is called. + */ + static void + clearInterruptFlags(InterruptFlags_t flags = InterruptFlags::All) + { + BDMA{{ instance }}->IFCR = flags.value << (static_cast(Id) * 4); + } + + /** + * Enable the IRQ vector of the channel + * + * @param[in] priority Priority of the IRQ + */ + static void + enableInterruptVector(uint32_t priority = 1) + { +%% if instance == "1" + NVIC_SetPriority(BDMA1_IRQn, priority); + NVIC_EnableIRQ(BDMA1_IRQn); +%% else + NVIC_SetPriority(irqs[uint32_t(Id)], priority); + NVIC_EnableIRQ(irqs[uint32_t(Id)]); +%% endif + } + /** + * Disable the IRQ vector of the channel + */ + static void + disableInterruptVector() + { +%% if instance == "1" + NVIC_DisableIRQ(BDMA1_IRQn); +%% else + NVIC_DisableIRQ(irqs[uint32_t(Id)]); +%% endif + } + + /** + * Enable the specified interrupt of the channel + */ + static void + enableInterrupt(InterruptEnable_t irq) + { + Hal::enableInterrupt(irq); + } + /** + * Disable the specified interrupt of the channel + */ + static void + disableInterrupt(InterruptEnable_t irq) + { + Hal::disableInterrupt(irq); + } + +%% if instance in ["", "2"] + template + using RequestMapping = bdma::RequestMapping; + + static constexpr IRQn_Type irqs[] = { +%% for channel in range(0, channel_count) + BDMA{{ instance }}_Channel{{ channel }}_IRQn{% if not loop.last %},{% endif %} +%% endfor + }; +%% endif + + private: + static inline BdmaBase::IrqHandler transferError { nullptr }; + static inline BdmaBase::IrqHandler halfTransferComplete { nullptr }; + static inline BdmaBase::IrqHandler transferComplete { nullptr }; + +%% if instance in ["", "2"] + struct MuxChannel + { + uint8_t muxChannel; +%% if instance != "" + uint8_t dmaInstance; +%% endif + uint8_t dmaChannel; + }; + + static constexpr std::array muxChannels = { +%% for channel in dma["mux-channels"][0]["mux-channel"] +%% if instance == "" + MuxChannel({{ channel.position }}, {{ channel["dma-channel"] }}){{ "," if not loop.last }} +%% else + MuxChannel({{ channel.position }}, {{ channel["dma-instance"] }}, {{ channel["dma-channel"] }}){{ "," if not loop.last }} +%% endif +%% endfor + }; +%% endif + }; +%% for channel in range(0, channel_count) + using Channel{{ channel }} = Channel; +%% endfor +}; + +%% if instance in ["", "2"] +/// @cond +/* + * Specialization of the RequestMapping. For all hardware supported by DMA the + * RequestMapping structure defines the channel and the Request. It can be used + * by hardware classes to verify that the provIded channel is valId and to + * get the value to set in setPeripheralRequest(). + * + * Example: + * template + * class SpiMaster1_Dma : public SpiMaster1 + * { + * using RxChannel = typename DmaRx::template RequestMapping::Channel; + * using TxChannel = typename DmaTx::template RequestMapping::Channel; + * static constexpr DmaRx::Request RxRequest = DmaRx::template RequestMapping::Request; + * static constexpr DmaTx::Request TxRequest = DmaTx::template RequestMapping::Request; + * + * ... + * }; + */ +namespace bdma +{ + +%% for request in dma["requests"][0]["request"] + +%% for signal in request.signal +%% if signal.name is defined +%% set request_signal = "BdmaBase::Signal::" + signal.name.capitalize() +%% else +%% set request_signal = "BdmaBase::Signal::NoSignal" +%% endif + +%% set peripheral = signal.driver.capitalize() +%% if signal.instance is defined + %% set peripheral = peripheral ~ signal.instance +%% endif +%% if peripheral == "Bdma" and instance in ["2"] + %% set peripheral = peripheral ~ instance +%% endif + + template + struct RequestMapping + { + using Channel = ChannelT; + static constexpr BdmaBase::Request Request = BdmaBase::Request::Request{{ request.position }}; + }; +%% endfor +%% endfor +} + +/// @endcond +%% endif + +} // namespace modm::platform + +#endif // MODM_STM32_BDMA_HPP diff --git a/src/modm/platform/dma/stm32-bdma/bdma_base.hpp.in b/src/modm/platform/dma/stm32-bdma/bdma_base.hpp.in new file mode 100644 index 0000000000..73341f5b49 --- /dev/null +++ b/src/modm/platform/dma/stm32-bdma/bdma_base.hpp.in @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2014, Kevin Läufer + * Copyright (c) 2014-2017, Niklas Hauser + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2021-2023, Christopher Durand + * + * 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_STM32_BDMA_BASE_HPP +#define MODM_STM32_BDMA_BASE_HPP + +#include +#include + +#include "../device.hpp" + +#include +#include + +namespace modm::platform +{ + +%% set reg_prefix = "BDMA_CCR" + +/** + * Common BDMA definitions + * + * @ingroup modm_platform_dma + */ +class BdmaBase +{ +public: + enum class + Channel + { + %% for channel in range(0, channel_count) + Channel{{ channel }}{% if loop.first %} = 0{% endif %}, + %% endfor + }; + + %% set request_count = namespace(max_requests = 0) + %% for request in dma["requests"][0]["request"] + %% if request_count.max_requests < request.position | int + %% set request_count.max_requests = request.position | int + %% endif + %% endfor + + enum class + Request + { + None = 0, + %% for request in range(1, request_count.max_requests + 1) + Request{{ request }}, + %% endfor + }; + + enum class + Priority : uint32_t + { + Low = 0, + Medium = {{ reg_prefix }}_PL_0, + High = {{ reg_prefix }}_PL_1, + VeryHigh = {{ reg_prefix }}_PL_1 | {{ reg_prefix }}_PL_0, + }; + + enum class + MemoryDataSize : uint32_t + { + Byte = 0, + Bit8 = Byte, + HalfWord = {{ reg_prefix }}_MSIZE_0, + Bit16 = HalfWord, + Word = {{ reg_prefix }}_MSIZE_1, + Bit32 = Word, + }; + + enum class + PeripheralDataSize : uint32_t + { + Byte = 0, + Bit8 = Byte, + HalfWord = {{ reg_prefix }}_PSIZE_0, + Bit16 = HalfWord, + Word = {{ reg_prefix }}_PSIZE_1, + Bit32 = Word, + }; + + enum class + MemoryIncrementMode : uint32_t + { + Fixed = 0, + Increment = {{ reg_prefix }}_MINC, ///< incremented according to MemoryDataSize + }; + + enum class + PeripheralIncrementMode : uint32_t + { + Fixed = 0, + Increment = {{ reg_prefix }}_PINC, ///< incremented according to PeripheralDataSize + }; + + enum class + CircularMode : uint32_t + { + Disabled = 0, + Enabled = {{ reg_prefix }}_CIRC, ///< circular mode + }; + + enum class + DataTransferDirection : uint32_t + { + /// Source: DMA_CPARx; Sink: DMA_CMARx + PeripheralToMemory = 0, + /// Source: DMA_CMARx; Sink: DMA_CPARx + MemoryToPeripheral = {{ reg_prefix }}_DIR, + /// Source: DMA_CPARx; Sink: DMA_CMARx + MemoryToMemory = {{ reg_prefix }}_MEM2MEM, + }; + + /** + * Peripheral signals that can be used in DMA channels + */ + enum class + Signal : uint8_t + { + NoSignal, +%% for signal in dma_signals + {{ signal }}, +%% endfor + }; + + enum class InterruptEnable : uint32_t + { + TransferComplete = {{ reg_prefix }}_TCIE, + HalfTransfer = {{ reg_prefix }}_HTIE, + TransferError = {{ reg_prefix }}_TEIE, + }; + MODM_FLAGS32(InterruptEnable); + + enum class InterruptFlags : uint8_t + { + Global = 0b0001, + TransferComplete = 0b0010, + HalfTransferComplete = 0b0100, + Error = 0b1000, + All = 0b1111, + }; + MODM_FLAGS32(InterruptFlags); + + using IrqHandler = void (*)(void); +}; + +} // namespace modm::platform + +#endif // MODM_STM32_BDMA_BASE_HPP diff --git a/src/modm/platform/dma/stm32-bdma/bdma_hal.hpp b/src/modm/platform/dma/stm32-bdma/bdma_hal.hpp new file mode 100644 index 0000000000..c471abf539 --- /dev/null +++ b/src/modm/platform/dma/stm32-bdma/bdma_hal.hpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32_BDMA_HAL_HPP +#define MODM_STM32_BDMA_HAL_HPP + +#include "bdma_base.hpp" + +namespace modm::platform +{ + +/** + * BDMA channel low-level register abstraction + * + * @tparam base_addr base address of the channel registers + * + * @ingroup modm_platform_dma + */ +template +class BdmaChannelHal : public BdmaBase +{ + static BDMA_Channel_TypeDef* + reg() + { + return reinterpret_cast(base_addr); + } + +public: + /** + * Configure the DMA channel (HAL) + * + * Stops the DMA channel and writes the new values to its control register. + * + * @param[in] direction Direction of the DMA channel + * @param[in] memoryDataSize Size of data in memory (byte, halfword, word) + * @param[in] peripheralDataSize Size of data in peripheral (byte, halfword, word) + * @param[in] memoryIncrement Defines whether the memory address is incremented + * after a transfer completed + * @param[in] peripheralIncrement Defines whether the peripheral address is + * incremented after a transfer completed + * @param[in] priority Priority of the DMA channel + * @param[in] circularMode Transfer data in circular mode? + */ + static void + configure(DataTransferDirection direction, MemoryDataSize memoryDataSize, + PeripheralDataSize peripheralDataSize, + MemoryIncrementMode memoryIncrement, + PeripheralIncrementMode peripheralIncrement, + Priority priority = Priority::Medium, + CircularMode circularMode = CircularMode::Disabled) + { + stop(); + + reg()->CCR = uint32_t(direction) | uint32_t(memoryDataSize) | + uint32_t(peripheralDataSize) | uint32_t(memoryIncrement) | + uint32_t(peripheralIncrement) | uint32_t(priority) | + uint32_t(circularMode); + } + + /** + * Start the transfer of the DMA channel + */ + static void + start() + { + reg()->CCR |= BDMA_CCR_EN; + } + + /** + * Stop a DMA channel transfer + */ + static void + stop() + { + reg()->CCR &= ~BDMA_CCR_EN; + while (reg()->CCR & BDMA_CCR_EN); // wait for stream to be stopped + } + + static DataTransferDirection + getDataTransferDirection() + { + return static_cast( + reg()->CCR & (BDMA_CCR_MEM2MEM | BDMA_CCR_DIR)); + } + + /** + * Set the memory 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 + setMemoryAddress(uintptr_t address) + { + reg()->CM0AR = address; + } + + /** + * Set the peripheral 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 + setPeripheralAddress(uintptr_t address) + { + reg()->CPAR = address; + } + + /** + * Enable/disable memory increment + * + * When enabled, the memory 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 + setMemoryIncrementMode(bool increment) + { + if (increment) + reg()->CCR |= uint32_t(MemoryIncrementMode::Increment); + else + reg()->CCR &= ~uint32_t(MemoryIncrementMode::Increment); + } + + /** + * Enable/disable peripheral increment + * + * When enabled, the peripheral 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 + setPeripheralIncrementMode(bool increment) + { + if (increment) + reg()->CCR |= uint32_t(PeripheralIncrementMode::Increment); + else + reg()->CCR &= ~uint32_t(PeripheralIncrementMode::Increment); + } + + /** + * Set length of data to transfer + */ + static void + setDataLength(std::size_t length) + { + reg()->CNDTR = length; + } + + /** + * Enable IRQ of this DMA channel (e.g. transfer complete or error) + */ + static void + enableInterrupt(InterruptEnable_t irq) + { + reg()->CCR |= irq.value; + } + /** + * Disable IRQ of this DMA channel (e.g. transfer complete or error) + */ + static void + disableInterrupt(InterruptEnable_t irq) + { + reg()->CCR &= ~(irq.value); + } +}; + +} // namespace modm::platform + +#endif // MODM_STM32_BDMA_HAL_HPP diff --git a/src/modm/platform/dma/stm32-bdma/module.lb b/src/modm/platform/dma/stm32-bdma/module.lb new file mode 100644 index 0000000000..5a6a2501da --- /dev/null +++ b/src/modm/platform/dma/stm32-bdma/module.lb @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2020, Mike Wolfram +# Copyright (c) 2021, Raphael Lehmann +# Copyright (c) 2021-2023, Christopher Durand +# +# 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/. +# ----------------------------------------------------------------------------- + +from collections import defaultdict +import re + +def _get_signal_names(driver): + signal_names = {} + for request_data in driver["requests"]: + for request in request_data["request"]: + for signal in request["signal"]: + if "name" in signal: + signal_name = signal["name"].capitalize() + signal_names[signal_name] = 1 + return sorted(list(set(signal_names))) + + +def _validate_channel_count(driver): + channel_count = 8 + assert len(driver["mux-channels"]) == 1 # only one DMAMUX instance is supported + channels = driver["mux-channels"][0]["mux-channel"] + instance_channels = defaultdict(list) + for channel in channels: + instance_channels[channel.get("dma-instance")].append(channel["dma-channel"]) + for instance in instance_channels: + channel_list = [int(c) for c in instance_channels[instance]] + channel_list.sort() + assert channel_list == list(range(0, channel_count)) + + +def _get_substitutions(device, instance = ""): + dma = device.get_driver("bdma") + return { + "target" : device.identifier, + "dma" : dma, + "dma_signals" : _get_signal_names(dma), + "channel_count" : 8, + "instance" : instance + } + + +class Instance(Module): + def __init__(self, instance): + self.instance = instance + + 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): + device = env[":target"] + driver = device.get_driver("bdma") + + env.substitutions = _get_substitutions(device, str(self.instance)) + env.outbasepath = "modm/src/modm/platform/dma" + + env.template("bdma.hpp.in", "bdma_{}.hpp".format(self.instance)) + env.template("bdma.cpp.in", "bdma_{}.cpp".format(self.instance)) + + +def init(module): + module.name = ":platform:bdma" + module.description = "Basic Direct Memory Access Controller (BDMA)" + + +def prepare(module, options): + device = options[":target"] + module.depends(":cmsis:device", ":platform:rcc") + + if not device.identifier.platform == "stm32": + return False + if not device.has_driver("bdma"): + return False + + driver = device.get_driver("bdma") + if "instance" in driver: + for instance in listify(driver["instance"]): + module.add_submodule(Instance(int(instance))) + + return True + + +def build(env): + device = env[":target"] + driver = device.get_driver("bdma") + + _validate_channel_count(driver) + + env.substitutions = _get_substitutions(device) + env.outbasepath = "modm/src/modm/platform/dma" + + env.copy("bdma_hal.hpp") + env.template("bdma_base.hpp.in") + if "instance" not in driver: + env.template("bdma.hpp.in") + env.template("bdma.cpp.in") diff --git a/tools/scripts/generate_hal_matrix.py b/tools/scripts/generate_hal_matrix.py index 3236066ba5..ac0bc792d3 100755 --- a/tools/scripts/generate_hal_matrix.py +++ b/tools/scripts/generate_hal_matrix.py @@ -76,7 +76,7 @@ def hal_get_modules(): modules = set() # We only care about some modm:platform:* modules here - not_interested = {"bitbang", "common", "heap", "core", "fault", "cortex-m", "uart.spi", "itm", "rtt", "pwm"} + not_interested = {"bitbang", "common", "heap", "core", "fault", "cortex-m", "uart.spi", "itm", "rtt", "pwm", "bdma"} 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) and From 5c7db3ef65a419fac1deeb2695bb4c56859726da Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 21 Sep 2023 02:25:01 +0200 Subject: [PATCH 111/159] [stm32] Make SPI and DAC driver compatible with BDMA --- src/modm/platform/dac/stm32/dac_dma.hpp.in | 4 +- .../platform/dac/stm32/dac_dma_impl.hpp.in | 20 +++++----- src/modm/platform/dma/stm32-bdma/module.lb | 8 ++-- src/modm/platform/dma/stm32/dma.hpp.in | 2 +- .../spi/stm32h7/spi_master_dma.hpp.in | 14 +++---- .../spi/stm32h7/spi_master_dma_impl.hpp.in | 37 ++++++++++--------- 6 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/modm/platform/dac/stm32/dac_dma.hpp.in b/src/modm/platform/dac/stm32/dac_dma.hpp.in index a2fed5d245..a2af4e65eb 100644 --- a/src/modm/platform/dac/stm32/dac_dma.hpp.in +++ b/src/modm/platform/dac/stm32/dac_dma.hpp.in @@ -127,8 +127,8 @@ public: * @param circularMode DMA circular mode setting * @param priority DMA transfer priority */ - static void configure(const void* data, size_t dataSize, DmaBase::CircularMode circularMode, - DmaBase::Priority priority = DmaBase::Priority::Medium); + static void configure(const void* data, size_t dataSize, DmaChannel::CircularMode circularMode, + DmaChannel::Priority priority = DmaChannel::Priority::Medium); /** * Set data to transfer diff --git a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in index 72c5e7cf43..f306f43e1f 100644 --- a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in +++ b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in @@ -107,26 +107,26 @@ bool template void -{{ channel }}::configure(const void* data, size_t dataLength, DmaBase::CircularMode circularMode, - DmaBase::Priority priority) +{{ channel }}::configure(const void* data, size_t dataLength, DmaChannel::CircularMode circularMode, + DmaChannel::Priority priority) { %% if target.family in ["f2", "f4", "f7"] - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::RequestMapping; %% else - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::RequestMapping; %% endif constexpr auto request = RequestMapping::Request; DmaChannel::configure( - DmaBase::DataTransferDirection::MemoryToPeripheral, - DmaBase::MemoryDataSize::HalfWord, + DmaChannel::DataTransferDirection::MemoryToPeripheral, + DmaChannel::MemoryDataSize::HalfWord, %% if target.family in ["f0", "f2", "f3", "f4", "f7", "h7"] - DmaBase::PeripheralDataSize::HalfWord, + DmaChannel::PeripheralDataSize::HalfWord, %% else - DmaBase::PeripheralDataSize::Word, + DmaChannel::PeripheralDataSize::Word, %% endif - DmaBase::MemoryIncrementMode::Increment, - DmaBase::PeripheralIncrementMode::Fixed, + DmaChannel::MemoryIncrementMode::Increment, + DmaChannel::PeripheralIncrementMode::Fixed, priority, circularMode ); diff --git a/src/modm/platform/dma/stm32-bdma/module.lb b/src/modm/platform/dma/stm32-bdma/module.lb index 5a6a2501da..aa24b03f1b 100644 --- a/src/modm/platform/dma/stm32-bdma/module.lb +++ b/src/modm/platform/dma/stm32-bdma/module.lb @@ -18,14 +18,14 @@ from collections import defaultdict import re def _get_signal_names(driver): - signal_names = {} + signal_names = set() for request_data in driver["requests"]: for request in request_data["request"]: for signal in request["signal"]: if "name" in signal: signal_name = signal["name"].capitalize() - signal_names[signal_name] = 1 - return sorted(list(set(signal_names))) + signal_names.add(signal_name) + return sorted(list(signal_names)) def _validate_channel_count(driver): @@ -42,7 +42,7 @@ def _validate_channel_count(driver): def _get_substitutions(device, instance = ""): - dma = device.get_driver("bdma") + dma = device.get_driver("bdma:stm32*") return { "target" : device.identifier, "dma" : dma, diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index 5cab771b7d..265f1954ba 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -95,7 +95,7 @@ public: * Class representing a DMA channel/stream */ template - class Channel + class Channel : public DmaBase { static_assert( %% for controller in dmaController diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in index 009fabe238..992a666a1e 100644 --- a/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in +++ b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in @@ -37,14 +37,12 @@ class SpiMaster{{ id }}_Dma : public modm::SpiMaster, { protected: struct Dma { - using RxChannel = typename DmaChannelRx::template RequestMapping< - Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Channel; - using TxChannel = typename DmaChannelTx::template RequestMapping< - Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Channel; - static constexpr DmaBase::Request RxRequest = DmaChannelRx::template RequestMapping< - Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Request; - static constexpr DmaBase::Request TxRequest = DmaChannelTx::template RequestMapping< - Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Request; + using RxChannel = DmaChannelRx; + using TxChannel = DmaChannelTx; + static constexpr DmaChannelRx::Request RxRequest = DmaChannelRx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaChannelRx::Signal::Rx>::Request; + static constexpr DmaChannelTx::Request TxRequest = DmaChannelTx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaChannelTx::Signal::Tx>::Request; }; %% if not use_fiber diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in index 10aa8d2ab2..a159b8f3b2 100644 --- a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in +++ b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in @@ -22,21 +22,24 @@ template ::initialize() { - Dma::RxChannel::configure(DmaBase::DataTransferDirection::PeripheralToMemory, - DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, - DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, - DmaBase::Priority::Medium); - Dma::RxChannel::disableInterruptVector(); - Dma::RxChannel::setPeripheralAddress(reinterpret_cast(Hal::receiveRegister())); - Dma::RxChannel::template setPeripheralRequest(); - - Dma::TxChannel::configure(DmaBase::DataTransferDirection::MemoryToPeripheral, - DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, - DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, - DmaBase::Priority::Medium); - Dma::TxChannel::disableInterruptVector(); - Dma::TxChannel::setPeripheralAddress(reinterpret_cast(Hal::transmitRegister())); - Dma::TxChannel::template setPeripheralRequest(); + using Rx = Dma::RxChannel; + using Tx = Dma::TxChannel; + + Rx::configure(Rx::DataTransferDirection::PeripheralToMemory, + Rx::MemoryDataSize::Byte, Rx::PeripheralDataSize::Byte, + Rx::MemoryIncrementMode::Increment, Rx::PeripheralIncrementMode::Fixed, + Rx::Priority::Medium); + Rx::disableInterruptVector(); + Rx::setPeripheralAddress(reinterpret_cast(Hal::receiveRegister())); + Rx::template setPeripheralRequest(); + + Tx::configure(Tx::DataTransferDirection::MemoryToPeripheral, + Tx::MemoryDataSize::Byte, Tx::PeripheralDataSize::Byte, + Tx::MemoryIncrementMode::Increment, Tx::PeripheralIncrementMode::Fixed, + Tx::Priority::Medium); + Tx::disableInterruptVector(); + Tx::setPeripheralAddress(reinterpret_cast(Hal::transmitRegister())); + Tx::template setPeripheralRequest(); %% if not use_fiber state = 0; @@ -150,7 +153,7 @@ modm::ResumableResult SpiMaster{{ id }}_Dma::transfer( const uint8_t* tx, uint8_t* rx, std::size_t length) { - using Flags = DmaBase::InterruptFlags; + using Flags = DmaChannelRx::InterruptFlags; %% if use_fiber startDmaTransfer(tx, rx, length); @@ -196,7 +199,7 @@ SpiMaster{{ id }}_Dma::transfer( default: if (!Hal::isTransferCompleted() or !(state & Bit2)) { if(rx) { - static DmaBase::InterruptFlags_t flags; + static typename DmaChannelRx::InterruptFlags_t flags; flags = DmaChannelRx::getInterruptFlags(); if (flags & Flags::Error) { // abort on DMA error From e6ffb1d04da95f28822dbba23f9976ff6cbf2cdb Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 2 Jan 2024 21:27:12 +0100 Subject: [PATCH 112/159] [ext] Update modm-devices submodule --- ext/modm-devices | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/modm-devices b/ext/modm-devices index 9079ec5722..3c437bbfa7 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit 9079ec5722d523ecff2585b3e6a651aea31961d6 +Subproject commit 3c437bbfa740d61d99a66de8a36d64281806fd54 From 2e40ab4a6f580c147cf3183a586e758f7c84d8a1 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 2 Jan 2024 21:56:31 +0100 Subject: [PATCH 113/159] [test] Add STM32H7 BDMA and SPI6 hardware unit test Only runs on Nucleo-H723ZG --- test/modm/platform/spi/stm32h7/module.lb | 35 ++++++++++++ .../platform/spi/stm32h7/spi_bdma_test.cpp | 54 +++++++++++++++++++ .../platform/spi/stm32h7/spi_bdma_test.hpp | 23 ++++++++ 3 files changed, 112 insertions(+) create mode 100644 test/modm/platform/spi/stm32h7/module.lb create mode 100644 test/modm/platform/spi/stm32h7/spi_bdma_test.cpp create mode 100644 test/modm/platform/spi/stm32h7/spi_bdma_test.hpp diff --git a/test/modm/platform/spi/stm32h7/module.lb b/test/modm/platform/spi/stm32h7/module.lb new file mode 100644 index 0000000000..4c19887b36 --- /dev/null +++ b/test/modm/platform/spi/stm32h7/module.lb @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024, Christopher Durand +# +# 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 = ":test:platform:spi" + module.description = "STM32H7 SPI BDMA test" + +def prepare(module, options): + target = options[":target"] + + identifier = target.identifier + if identifier.platform != "stm32" or identifier.family != "h7": + return False + + module.depends(":platform:bdma", ":platform:dma", ":platform:spi:6") + return True + +def build(env): + if not env.has_module(":board:nucleo-h723zg"): + env.log.warn("The SPI test uses hardcoded GPIO pins." + "Please make sure the pins are safe to use on other hardware.") + return + + env.outbasepath = "modm-test/src/modm-test/platform/spi_test" + env.copy("spi_bdma_test.hpp") + env.copy("spi_bdma_test.cpp") diff --git a/test/modm/platform/spi/stm32h7/spi_bdma_test.cpp b/test/modm/platform/spi/stm32h7/spi_bdma_test.cpp new file mode 100644 index 0000000000..b177057401 --- /dev/null +++ b/test/modm/platform/spi/stm32h7/spi_bdma_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Christopher Durand + * + * 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_bdma_test.hpp" + +#include +#include + +using namespace modm::platform; + +using Spi = SpiMaster6_Dma; +using Miso = GpioA6; +using Sck = GpioC12; + +void +SpiBdmaTest::setUp() +{ + Bdma::enable(); + Spi::connect(); + Spi::initialize(); +} + +void +SpiBdmaTest::testReceive() +{ + constexpr std::array ones{0xff, 0xff, 0xff, 0xff}; + constexpr std::array zeros{}; + modm_section(".data_d3_sram") static std::array buffer{}; + + Miso::configure(Miso::InputType::PullUp); + modm::delay_ns(500); + Spi::transferBlocking(nullptr, buffer.data(), buffer.size()); + TEST_ASSERT_TRUE(buffer == ones); + + Miso::configure(Miso::InputType::PullDown); + modm::delay_ns(500); + Spi::transferBlocking(nullptr, buffer.data(), buffer.size()); + TEST_ASSERT_TRUE(buffer == zeros); + + Miso::configure(Miso::InputType::PullUp); + modm::delay_ns(500); + Spi::transferBlocking(nullptr, buffer.data(), buffer.size()); + TEST_ASSERT_TRUE(buffer == ones); + + Miso::configure(Miso::InputType::Floating); +} diff --git a/test/modm/platform/spi/stm32h7/spi_bdma_test.hpp b/test/modm/platform/spi/stm32h7/spi_bdma_test.hpp new file mode 100644 index 0000000000..4e8d3894a4 --- /dev/null +++ b/test/modm/platform/spi/stm32h7/spi_bdma_test.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, Christopher Durand + * + * 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 + +/// @ingroup modm_test_test_platform_spi +class SpiBdmaTest : public unittest::TestSuite +{ +public: + void + setUp() override; + + void + testReceive(); +}; From e5fb526cc986ebdf661a8a85817f47c054bada5b Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Fri, 22 Sep 2023 12:48:16 +0200 Subject: [PATCH 114/159] [board] Add Nucleo-G070RB --- README.md | 15 +-- src/modm/board/nucleo_g070rb/board.hpp | 158 +++++++++++++++++++++++++ src/modm/board/nucleo_g070rb/board.xml | 15 +++ src/modm/board/nucleo_g070rb/module.lb | 41 +++++++ 4 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 src/modm/board/nucleo_g070rb/board.hpp create mode 100644 src/modm/board/nucleo_g070rb/board.xml create mode 100644 src/modm/board/nucleo_g070rb/module.lb diff --git a/README.md b/README.md index 5147d6d0c9..3d6b9e4338 100644 --- a/README.md +++ b/README.md @@ -657,40 +657,41 @@ We have out-of-box support for many development boards including documentation. NUCLEO-F746ZG NUCLEO-F767ZI +NUCLEO-G070RB NUCLEO-G071RB NUCLEO-G431KB -NUCLEO-G431RB +NUCLEO-G431RB NUCLEO-G474RE NUCLEO-H723ZG NUCLEO-H743ZI -NUCLEO-L031K6 +NUCLEO-L031K6 NUCLEO-L053R8 NUCLEO-L152RE NUCLEO-L432KC -NUCLEO-L452RE +NUCLEO-L452RE NUCLEO-L476RG NUCLEO-L496ZG-P NUCLEO-L552ZE-Q -NUCLEO-U575ZI-Q +NUCLEO-U575ZI-Q OLIMEXINO-STM32 Raspberry Pi Pico SAMD21-MINI -SAMD21-XPLAINED-PRO +SAMD21-XPLAINED-PRO SAME54-XPLAINED-PRO SAME70-XPLAINED SAMG55-XPLAINED-PRO -SAMV71-XPLAINED-ULTRA +SAMV71-XPLAINED-ULTRA Smart Response XE STM32-F4VE STM32F030-DEMO -THINGPLUS-RP2040 +THINGPLUS-RP2040 diff --git a/src/modm/board/nucleo_g070rb/board.hpp b/src/modm/board/nucleo_g070rb/board.hpp new file mode 100644 index 0000000000..91957c0e92 --- /dev/null +++ b/src/modm/board/nucleo_g070rb/board.hpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019, Niklas Hauser + * Copyright (c) 2023, Christopher Durand + * + * 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_STM32_NUCLEO_G070RB_HPP +#define MODM_STM32_NUCLEO_G070RB_HPP + +#include +#include +#include +/// @ingroup modm_board_nucleo_g070rb +#define MODM_BOARD_HAS_LOGGER + +using namespace modm::platform; + +namespace Board +{ +/// @ingroup modm_board_nucleo_g070rb +/// @{ +using namespace modm::literals; + +/// STM32G070RB running at 64MHz generated from the internal 16MHz oscillator +struct SystemClock +{ + static constexpr uint32_t Frequency = 64_MHz; + static constexpr uint32_t Ahb = Frequency; + static constexpr uint32_t Apb = Frequency; + + static constexpr uint32_t Rng = Ahb; + static constexpr uint32_t Crc = Ahb; + static constexpr uint32_t Flash = Ahb; + static constexpr uint32_t Exti = Ahb; + static constexpr uint32_t Rcc = Ahb; + static constexpr uint32_t Dmamux = Ahb; + static constexpr uint32_t Dma = Ahb; + + static constexpr uint32_t Dbg = Apb; + static constexpr uint32_t Timer17 = Apb; + static constexpr uint32_t Timer16 = Apb; + static constexpr uint32_t Timer15 = Apb; + static constexpr uint32_t Usart1 = Apb; + static constexpr uint32_t Spi1 = Apb; + static constexpr uint32_t I2s1 = Apb; + static constexpr uint32_t Timer1 = Apb; + static constexpr uint32_t Adc1 = Apb; + static constexpr uint32_t ItLine = Apb; + static constexpr uint32_t SysCfg = Apb; + static constexpr uint32_t Tamp = Apb; + static constexpr uint32_t Bkp = Apb; + static constexpr uint32_t Ucpd2 = Apb; + static constexpr uint32_t Ucpd1 = Apb; + static constexpr uint32_t Dac = Apb; + static constexpr uint32_t Pwr = Apb; + static constexpr uint32_t I2c2 = Apb; + static constexpr uint32_t I2c1 = Apb; + static constexpr uint32_t Usart4 = Apb; + static constexpr uint32_t Usart3 = Apb; + static constexpr uint32_t Usart2 = Apb; + static constexpr uint32_t Spi2 = Apb; + static constexpr uint32_t Iwdg = Apb; + static constexpr uint32_t Wwdg = Apb; + static constexpr uint32_t Rtc = Apb; + static constexpr uint32_t Timer14 = Apb; + static constexpr uint32_t Timer7 = Apb; + static constexpr uint32_t Timer6 = Apb; + static constexpr uint32_t Timer3 = Apb; + + static bool inline + enable() + { + Rcc::enableInternalClock(); // 16MHz + // (internal clock / 1_M) * 8_N / 2_R = 128MHz / 2 = 64MHz + const Rcc::PllFactors pllFactors{ + .pllM = 1, + .pllN = 8, + .pllR = 2, + }; + Rcc::enablePll(Rcc::PllSource::InternalClock, pllFactors); + Rcc::setFlashLatency(); + // switch system clock to PLL output + Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); + Rcc::setAhbPrescaler(Rcc::AhbPrescaler::Div1); + Rcc::setApbPrescaler(Rcc::ApbPrescaler::Div1); + // update frequencies for busy-wait delay functions + Rcc::updateCoreFrequency(); + + return true; + } +}; + +// Arduino Footprint +using A0 = GpioA0; +using A1 = GpioA1; +using A2 = GpioA4; +using A3 = GpioB1; +using A4 = GpioB11; +using A5 = GpioB12; + +using D0 = GpioC5; +using D1 = GpioC4; +using D2 = GpioA10; +using D3 = GpioB3; +using D4 = GpioB5; +using D5 = GpioB4; +using D6 = GpioB14; +using D7 = GpioA8; +using D8 = GpioA9; +using D9 = GpioC7; +using D10 = GpioB0; +using D11 = GpioA7; +using D12 = GpioA6; +using D13 = GpioA5; +using D14 = GpioB9; +using D15 = GpioB8; + +using Button = GpioInverted; +using LedD13 = D13; + +using Leds = SoftwareGpioPort< LedD13 >; +/// @} + +namespace stlink +{ +/// @ingroup modm_board_nucleo_g070rb +/// @{ +using Rx = GpioInputA3; +using Tx = GpioOutputA2; +using Uart = Usart2; +/// @} +} + +/// @ingroup modm_board_nucleo_g070rb +/// @{ +using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; + +inline void +initialize() +{ + SystemClock::enable(); + SysTickTimer::initialize(); + + stlink::Uart::connect(); + stlink::Uart::initialize(); + + Button::setInput(); +} +/// @} + +} + +#endif // MODM_STM32_NUCLEO_G070RB_HPP diff --git a/src/modm/board/nucleo_g070rb/board.xml b/src/modm/board/nucleo_g070rb/board.xml new file mode 100644 index 0000000000..2aa603ab34 --- /dev/null +++ b/src/modm/board/nucleo_g070rb/board.xml @@ -0,0 +1,15 @@ + + + + ../../../../repo.lb + + + + + + + + + modm:board:nucleo-g070rb + + diff --git a/src/modm/board/nucleo_g070rb/module.lb b/src/modm/board/nucleo_g070rb/module.lb new file mode 100644 index 0000000000..148f459ada --- /dev/null +++ b/src/modm/board/nucleo_g070rb/module.lb @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2019, Niklas Hauser +# Copyright (c) 2023, Christopher Durand +# +# 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:nucleo-g070rb" + module.description = """\ +# NUCLEO-G070RB + +[Nucleo kit for STM32G070RB](https://www.st.com/en/evaluation-tools/nucleo-g070rb.html) +""" + +def prepare(module, options): + if not options[":target"].partname.startswith("stm32g070rbt"): + return False + + module.depends(":platform:core", ":platform:gpio", ":platform:clock", ":platform:uart:2", + ":debug", ":architecture:clock") + return True + +def build(env): + env.outbasepath = "modm/src/modm/board" + env.substitutions = { + "with_logger": True, + "with_assert": env.has_module(":architecture:assert") + } + env.template("../board.cpp.in", "board.cpp") + env.copy('.') + + env.outbasepath = "modm/openocd/modm/board/" + env.copy(repopath("tools/openocd/modm/st_nucleo_g0.cfg"), "st_nucleo_g0.cfg") + env.collect(":build:openocd.source", "modm/board/st_nucleo_g0.cfg") From 6de70bacd25b7e5ec841345b7734d9131632ba13 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Fri, 22 Sep 2023 12:49:16 +0200 Subject: [PATCH 115/159] [example] Add Nucleo-G070RB blink example --- .github/workflows/linux.yml | 2 +- examples/nucleo_g070rb/blink/main.cpp | 44 ++++++++++++++++++++++++ examples/nucleo_g070rb/blink/project.xml | 11 ++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 examples/nucleo_g070rb/blink/main.cpp create mode 100644 examples/nucleo_g070rb/blink/project.xml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9de02e20ef..c6da2de592 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -153,7 +153,7 @@ jobs: - name: Examples STM32G0 Series if: always() run: | - (cd examples && ../tools/scripts/examples_compile.py nucleo_g071rb) + (cd examples && ../tools/scripts/examples_compile.py nucleo_g070rb nucleo_g071rb) - name: Examples STM32L0 Series if: always() run: | diff --git a/examples/nucleo_g070rb/blink/main.cpp b/examples/nucleo_g070rb/blink/main.cpp new file mode 100644 index 0000000000..a6f817cc72 --- /dev/null +++ b/examples/nucleo_g070rb/blink/main.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019, 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 +#include + +using namespace Board; + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + LedD13::setOutput(modm::Gpio::Low); + + // 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; + + uint32_t counter{0}; + modm::PeriodicTimer timer{500ms}; + + while (true) + { + if (timer.execute()) + { + LedD13::toggle(); + + MODM_LOG_INFO << "loop: " << counter++ << modm::endl; + } + } + + return 0; +} diff --git a/examples/nucleo_g070rb/blink/project.xml b/examples/nucleo_g070rb/blink/project.xml new file mode 100644 index 0000000000..001c5a5fc0 --- /dev/null +++ b/examples/nucleo_g070rb/blink/project.xml @@ -0,0 +1,11 @@ + + modm:nucleo-g070rb + + + + + modm:platform:gpio + modm:processing:timer + modm:build:scons + + From a3cb6412ecfe52a43f8ef1c1661ea3362d1d0318 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 16 Nov 2023 13:50:07 +0100 Subject: [PATCH 116/159] [stm32] Add regular conversion sequences to F3 ADC driver --- src/modm/platform/adc/stm32f3/adc.hpp.in | 21 ++++++- src/modm/platform/adc/stm32f3/adc_impl.hpp.in | 56 ++++++++++++++++++- src/modm/platform/adc/stm32f3/module.lb | 2 +- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index e2814af448..9c43011968 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -15,10 +15,13 @@ #ifndef MODM_STM32F3_ADC{{ id }}_HPP #define MODM_STM32F3_ADC{{ id }}_HPP -#include +#include +#include +#include #include "../device.hpp" #include #include +#include namespace modm { @@ -260,6 +263,13 @@ public: Cycles602 = 0b111, //< 601.5 ADC clock cycles }; %% endif + + struct SequenceChannel + { + Channel channel{}; + SampleTime sampleTime{}; + }; + enum class CalibrationMode : uint32_t { SingleEndedInputsMode = 0, @@ -414,6 +424,9 @@ public: setChannel(const Channel channel, const SampleTime sampleTime=static_cast(0b000)); + static inline bool + setChannelSequence(std::span sequence); + /// Setting the channel for a Pin template< class Gpio > static inline bool @@ -464,6 +477,9 @@ public: static inline void startConversion(); + static inline void + stopConversion(); + /** * @return If the conversion is finished. * @pre A conversion should have been started with startConversion() @@ -471,6 +487,9 @@ public: static inline bool isConversionFinished(); + static inline bool + isConversionSequenceFinished(); + /** * Start a new injected conversion sequence. * diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index b273c02852..412fed3b10 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -211,7 +211,6 @@ modm::platform::Adc{{ id }}::configureChannel(Channel channel, } - bool modm::platform::Adc{{ id }}::setChannel(Channel channel, SampleTime sampleTime) @@ -224,6 +223,42 @@ modm::platform::Adc{{ id }}::setChannel(Channel channel, return true; } +bool +modm::platform::Adc{{ id }}::setChannelSequence(std::span sequence) +{ + if (sequence.size() > 16 || sequence.size() == 0) { + return false; + } + + ADC{{ id }}->SQR1 = sequence.size() - 1; + + for (const auto [i, config] : modm::enumerate(sequence)) { + const auto [channel, sampleTime] = config; + if (!configureChannel(channel, sampleTime)) { + return false; + } + + if (i < 4) { + const auto shift = (i + 1) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR1 = (ADC{{ id }}->SQR1 & ~mask) | (std::to_underlying(channel) << shift); + } else if (i < 9) { + const auto shift = (i - 4) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR2 = (ADC{{ id }}->SQR2 & ~mask) | (std::to_underlying(channel) << shift); + } else if (i < 14) { + const auto shift = (i - 9) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR3 = (ADC{{ id }}->SQR3 & ~mask) | (std::to_underlying(channel) << shift); + } else { + const auto shift = (i - 14) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR4 = (ADC{{ id }}->SQR4 & ~mask) | (std::to_underlying(channel) << shift); + } + } + return true; +} + void modm::platform::Adc{{ id }}::setFreeRunningMode(const bool enable) { @@ -239,17 +274,34 @@ modm::platform::Adc{{ id }}::startConversion() { // TODO: maybe add more interrupt flags acknowledgeInterruptFlags(InterruptFlag::EndOfRegularConversion | - InterruptFlag::EndOfSampling | InterruptFlag::Overrun); + InterruptFlag::EndOfSampling | InterruptFlag::Overrun | + InterruptFlag::EndOfRegularSequenceOfConversions); // starts single conversion for the regular group ADC{{ id }}->CR |= ADC_CR_ADSTART; } +void +modm::platform::Adc{{ id }}::stopConversion() +{ + ADC{{ id }}->CR |= ADC_CR_ADSTP | ADC_CR_JADSTP; + while ((ADC{{ id }}->CR & (ADC_CR_ADSTP | ADC_CR_JADSTP)) != 0); + + acknowledgeInterruptFlags(InterruptFlag::EndOfRegularConversion | + InterruptFlag::EndOfSampling | InterruptFlag::Overrun); +} + bool modm::platform::Adc{{ id }}::isConversionFinished() { return static_cast(getInterruptFlags() & InterruptFlag::EndOfRegularConversion); } +bool +modm::platform::Adc{{ id }}::isConversionSequenceFinished() +{ + return static_cast(getInterruptFlags() & InterruptFlag::EndOfRegularSequenceOfConversions); +} + void modm::platform::Adc{{ id }}::startInjectedConversionSequence() { diff --git a/src/modm/platform/adc/stm32f3/module.lb b/src/modm/platform/adc/stm32f3/module.lb index ca8fc074bc..636966a0e1 100644 --- a/src/modm/platform/adc/stm32f3/module.lb +++ b/src/modm/platform/adc/stm32f3/module.lb @@ -21,7 +21,7 @@ class Instance(Module): module.description = "Instance {}".format(self.instance) def prepare(self, module, options): - module.depends(":platform:adc") + module.depends(":platform:adc", ":math:algorithm") return True def build(self, env): From d941ea45a4f77e70fa356c35a5ebb664878d70d1 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 1 Feb 2024 13:52:48 +0100 Subject: [PATCH 117/159] [stm32] Add basic DMA support to F3 ADC driver --- src/modm/platform/adc/stm32f3/adc.hpp.in | 29 +++++++++++++++++++ src/modm/platform/adc/stm32f3/adc_impl.hpp.in | 7 +++++ 2 files changed, 36 insertions(+) diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index 9c43011968..893a0f64a2 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -290,6 +290,25 @@ public: %% endif }; + enum class DmaMode : uint32_t + { + Disabled = 0, +%% if target["family"] in ["h7"] and resolution == 12: + OneShot = ADC3_CFGR_DMAEN, + Circular = ADC3_CFGR_DMACFG | ADC3_CFGR_DMAEN, + Mask = Circular +%% elif target["family"] in ["h7"] and resolution == 16: + OneShot = ADC_CFGR_DMNGT_0, + Dfsdm = ADC_CFGR_DMNGT_1, + Circular = ADC_CFGR_DMNGT_1 | ADC_CFGR_DMNGT_0, + Mask = ADC_CFGR_DMNGT_Msk +%% else + OneShot = ADC_CFGR_DMAEN, + Circular = ADC_CFGR_DMACFG | ADC_CFGR_DMAEN, + Mask = Circular +%% endif + }; + enum class Interrupt : uint32_t { Ready = ADC_IER_ADRDYIE, @@ -581,6 +600,16 @@ public: static inline void acknowledgeInterruptFlags(const InterruptFlag_t flags); + /// @return ADC data register pointer, for DMA use only. + static inline volatile uint32_t* + dataRegister() + { + return &(ADC{{ id }}->DR); + } + + static inline void + setDmaMode(DmaMode mode); + private: static inline bool configureChannel(Channel channel, SampleTime sampleTime); diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index 412fed3b10..a8b59115bf 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -357,6 +357,13 @@ modm::platform::Adc{{ id }}::isInjectedConversionFinished() return static_cast(getInterruptFlags() & InterruptFlag::EndOfInjectedSequenceOfConversions); } +void +modm::platform::Adc{{ id }}::setDmaMode(DmaMode mode) +{ + constexpr uint32_t mask = std::to_underlying(DmaMode::Mask); + ADC{{ id }}->CFGR = (ADC{{ id }}->CFGR & ~mask) | std::to_underlying(mode); +} + // ---------------------------------------------------------------------------- void modm::platform::Adc{{ id }}::enableInterruptVector(const uint32_t priority, From fe4cbc50569cc28e46943113882deaaa6363151f Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 1 Feb 2024 20:50:26 +0100 Subject: [PATCH 118/159] [example] Add STM32G4 ADC example with conversion sequence and DMA --- .../nucleo_g474re/adc_sequence_dma/main.cpp | 76 +++++++++++++++++++ .../adc_sequence_dma/project.xml | 12 +++ 2 files changed, 88 insertions(+) create mode 100644 examples/nucleo_g474re/adc_sequence_dma/main.cpp create mode 100644 examples/nucleo_g474re/adc_sequence_dma/project.xml diff --git a/examples/nucleo_g474re/adc_sequence_dma/main.cpp b/examples/nucleo_g474re/adc_sequence_dma/main.cpp new file mode 100644 index 0000000000..9a04683e9c --- /dev/null +++ b/examples/nucleo_g474re/adc_sequence_dma/main.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2024, Christopher Durand + * + * 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 + +int main() +{ + Board::initialize(); + + MODM_LOG_INFO << "STM32G4 ADC regular conversion sequence example" << modm::endl; + + // max. ADC clock for STM32G474: 60 MHz + // 170 MHz AHB clock / 4 = 42.5 MHz + Adc1::initialize( + Adc1::ClockMode::SynchronousPrescaler4, + Adc1::ClockSource::SystemClock, + Adc1::Prescaler::Disabled, + Adc1::CalibrationMode::SingleEndedInputsMode, + true); + + Adc1::connect(); + + constexpr auto sampleTime = Adc1::SampleTime::Cycles641; + constexpr std::array sequence{{ + // Perform dummy conversion (ADC errata 2.7.7 and 2.7.8) + {Adc1::getPinChannel(), sampleTime}, + // Read A0, A1, A3, A4 + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime} + }}; + Adc1::setChannelSequence(sequence); + Adc1::setDmaMode(Adc1::DmaMode::OneShot); + + Dma1::enable(); + using DmaChannel = Dma1::Channel1; + DmaChannel::configure(Dma1::DataTransferDirection::PeripheralToMemory, Dma1::MemoryDataSize::Bit16, + Dma1::PeripheralDataSize::Bit16, Dma1::MemoryIncrementMode::Increment, + Dma1::PeripheralIncrementMode::Fixed); + DmaChannel::setPeripheralAddress(reinterpret_cast(Adc1::dataRegister())); + constexpr auto request = DmaChannel::RequestMapping::Request; + DmaChannel::setPeripheralRequest(); + + std::array samples{}; + + while (true) + { + DmaChannel::setMemoryAddress(reinterpret_cast(samples.data())); + DmaChannel::setDataLength(samples.size()); + DmaChannel::start(); + Adc1::startConversion(); + + while (!(DmaChannel::getInterruptFlags() & Dma1::InterruptFlags::TransferComplete)); + DmaChannel::stop(); + + const double factor = 3.3 / 4095; + MODM_LOG_INFO.printf("A0: %1.3f V\n", samples[1] * factor); + MODM_LOG_INFO.printf("A1: %1.3f V\n", samples[2] * factor); + MODM_LOG_INFO.printf("A3: %1.3f V\n", samples[3] * factor); + MODM_LOG_INFO.printf("A4: %1.3f V\n", samples[4] * factor); + MODM_LOG_INFO << '\n'; + modm::delay(500ms); + } + + return 0; +} diff --git a/examples/nucleo_g474re/adc_sequence_dma/project.xml b/examples/nucleo_g474re/adc_sequence_dma/project.xml new file mode 100644 index 0000000000..35d6e6add3 --- /dev/null +++ b/examples/nucleo_g474re/adc_sequence_dma/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-g474re + + + + + modm:debug + modm:platform:adc:1 + modm:platform:dma + modm:build:scons + + From 41285482f6b444c58d056d7289d62a311e51e1a5 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Wed, 17 Jan 2024 20:18:57 +0100 Subject: [PATCH 119/159] [clock] Add LSI and HSI values to RCC class on STM32 --- repo.lb | 1 + src/modm/platform/clock/stm32/module.lb | 24 ++++++++++++------------ src/modm/platform/clock/stm32/rcc.hpp.in | 4 +++- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/repo.lb b/repo.lb index ccecae382c..b72e6996b8 100644 --- a/repo.lb +++ b/repo.lb @@ -257,6 +257,7 @@ def init(repo): repo.add_filter("modm.posixify", posixify) repo.add_filter("modm.ord", lambda letter: ord(letter[0].lower()) - ord("a")) repo.add_filter("modm.chr", lambda num: chr(num + ord("A"))) + repo.add_filter("modm.digsep", lambda num: f"{num:,}".replace(",", "'")) # Compute the available data from modm-devices devices = DevicesCache() diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 65d5cd9024..ceda4c17db 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -36,26 +36,26 @@ def build(env): properties["core"] = core = device.get_driver("core")["type"] if target["family"] in ["f1", "f3"]: - properties["hsi_frequency"] = 8 - properties["lsi_frequency"] = 40 + properties["hsi_frequency"] = 8_000_000 + properties["lsi_frequency"] = 40_000 properties["boot_frequency"] = properties["hsi_frequency"] elif target["family"] in ["h7"]: - properties["hsi_frequency"] = 64 - properties["lsi_frequency"] = 32 + properties["hsi_frequency"] = 64_000_000 + properties["lsi_frequency"] = 32_000 properties["boot_frequency"] = properties["hsi_frequency"] elif target["family"] in ["l0", "l1"]: - properties["hsi_frequency"] = 16 - properties["lsi_frequency"] = 37 - properties["msi_frequency"] = 2.097 + properties["hsi_frequency"] = 16_000_000 + properties["lsi_frequency"] = 37_000 + properties["msi_frequency"] = 2_097_000 properties["boot_frequency"] = properties["msi_frequency"] elif target["family"] in ["l5"]: - properties["hsi_frequency"] = 16 - properties["lsi_frequency"] = 32 - properties["msi_frequency"] = 4 + properties["hsi_frequency"] = 16_000_000 + properties["lsi_frequency"] = 32_000 + properties["msi_frequency"] = 4_000_000 properties["boot_frequency"] = properties["msi_frequency"] else: - properties["hsi_frequency"] = 16 - properties["lsi_frequency"] = 32 + properties["hsi_frequency"] = 16_000_000 + properties["lsi_frequency"] = 32_000 properties["boot_frequency"] = properties["hsi_frequency"] # TODO: Move this data into the device files diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index a958d38f41..72a379e84a 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -39,7 +39,9 @@ namespace modm::platform class Rcc { public: - static constexpr uint32_t BootFrequency = {{ "{0:,}".format((1000000 * boot_frequency)|int).replace(',', "'") }}; + static constexpr uint32_t LsiFrequency = {{ lsi_frequency | modm.digsep }}; + static constexpr uint32_t HsiFrequency = {{ hsi_frequency | modm.digsep }}; + static constexpr uint32_t BootFrequency = {{ boot_frequency | modm.digsep }}; enum class PllSource : uint32_t From 7f878401f2c4e1fe61cf22934b719ba5e3d4f043 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 28 Jan 2024 17:32:55 +0100 Subject: [PATCH 120/159] [board] Add IWDG frequency to SystemClock --- src/modm/board/black_pill_f103/board.hpp | 1 + src/modm/board/black_pill_f411/board.hpp | 1 + src/modm/board/blue_pill_f103/board.hpp | 1 + src/modm/board/devebox_stm32f4xx/board.hpp | 1 + src/modm/board/devebox_stm32h750vb/board.hpp | 1 + src/modm/board/disco_f051r8/board.hpp | 1 + src/modm/board/disco_f072rb/board.hpp | 1 + src/modm/board/disco_f100rb/board.hpp | 1 + src/modm/board/disco_f303vc/board.hpp | 1 + src/modm/board/disco_f401vc/board.hpp | 1 + src/modm/board/disco_f407vg/board.hpp | 1 + src/modm/board/disco_f429zi/board.hpp | 1 + src/modm/board/disco_f469ni/board.hpp.in | 1 + src/modm/board/disco_f746ng/board.hpp | 1 + src/modm/board/disco_f769ni/board.hpp | 1 + src/modm/board/disco_l152rc/board.hpp | 1 + src/modm/board/disco_l476vg/board.hpp | 1 + src/modm/board/nucleo_f031k6/board.hpp | 1 + src/modm/board/nucleo_f042k6/board.hpp | 1 + src/modm/board/nucleo_f072rb/board.hpp | 1 + src/modm/board/nucleo_f091rc/board.hpp | 1 + src/modm/board/nucleo_f103rb/board.hpp | 1 + src/modm/board/nucleo_f303k8/board.hpp | 1 + src/modm/board/nucleo_f303re/board.hpp | 1 + src/modm/board/nucleo_f334r8/board.hpp | 1 + src/modm/board/nucleo_f401re/board.hpp | 1 + src/modm/board/nucleo_f411re/board.hpp | 1 + src/modm/board/nucleo_f429zi/board.hpp | 1 + src/modm/board/nucleo_f446re/board.hpp | 1 + src/modm/board/nucleo_f446ze/board.hpp | 1 + src/modm/board/nucleo_f746zg/board.hpp | 1 + src/modm/board/nucleo_f767zi/board.hpp | 1 + src/modm/board/nucleo_g071rb/board.hpp | 2 +- src/modm/board/nucleo_g431kb/board.hpp | 1 + src/modm/board/nucleo_g431rb/board.hpp | 1 + src/modm/board/nucleo_g474re/board.hpp | 1 + src/modm/board/nucleo_h723zg/board.hpp | 1 + src/modm/board/nucleo_h743zi/board.hpp | 1 + src/modm/board/nucleo_l031k6/board.hpp | 1 + src/modm/board/nucleo_l053r8/board.hpp | 1 + src/modm/board/nucleo_l152re/board.hpp | 1 + src/modm/board/nucleo_l432kc/board.hpp | 1 + src/modm/board/nucleo_l452re/board.hpp | 1 + src/modm/board/nucleo_l476rg/board.hpp | 1 + src/modm/board/nucleo_l496zg-p/board.hpp | 1 + src/modm/board/nucleo_l552ze-q/board.hpp | 1 + src/modm/board/nucleo_u575zi-q/board.hpp | 1 + src/modm/board/olimexino_stm32/board.hpp | 1 + src/modm/board/stm32_f4ve/board.hpp | 1 + src/modm/board/stm32f030f4p6_demo/board.hpp | 1 + 50 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/modm/board/black_pill_f103/board.hpp b/src/modm/board/black_pill_f103/board.hpp index b14554a88a..ea441ab6a9 100644 --- a/src/modm/board/black_pill_f103/board.hpp +++ b/src/modm/board/black_pill_f103/board.hpp @@ -57,6 +57,7 @@ struct SystemClock static constexpr uint32_t Timer4 = Apb1Timer; static constexpr uint32_t Usb = Ahb / 1.5; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/black_pill_f411/board.hpp b/src/modm/board/black_pill_f411/board.hpp index 64b4e2eb79..fce7c901ae 100644 --- a/src/modm/board/black_pill_f411/board.hpp +++ b/src/modm/board/black_pill_f411/board.hpp @@ -59,6 +59,7 @@ struct SystemClock static constexpr uint32_t Timer11 = Apb2Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/blue_pill_f103/board.hpp b/src/modm/board/blue_pill_f103/board.hpp index accd49906b..dcd053c20c 100644 --- a/src/modm/board/blue_pill_f103/board.hpp +++ b/src/modm/board/blue_pill_f103/board.hpp @@ -57,6 +57,7 @@ struct SystemClock static constexpr uint32_t Timer4 = Apb1Timer; static constexpr uint32_t Usb = Ahb / 1.5; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/devebox_stm32f4xx/board.hpp b/src/modm/board/devebox_stm32f4xx/board.hpp index e3867b6424..241df28de9 100644 --- a/src/modm/board/devebox_stm32f4xx/board.hpp +++ b/src/modm/board/devebox_stm32f4xx/board.hpp @@ -74,6 +74,7 @@ struct SystemClock static constexpr uint32_t Timer12 = Apb1Timer; static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/devebox_stm32h750vb/board.hpp b/src/modm/board/devebox_stm32h750vb/board.hpp index c8d74b25a1..721e1b73ba 100644 --- a/src/modm/board/devebox_stm32h750vb/board.hpp +++ b/src/modm/board/devebox_stm32h750vb/board.hpp @@ -94,6 +94,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb2Timer; static constexpr uint32_t Usb = 48_MHz; // From PLL3Q + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f051r8/board.hpp b/src/modm/board/disco_f051r8/board.hpp index 4926dd561c..13db330f32 100644 --- a/src/modm/board/disco_f051r8/board.hpp +++ b/src/modm/board/disco_f051r8/board.hpp @@ -32,6 +32,7 @@ struct SystemClock static constexpr int Usart1 = Frequency; static constexpr int Usart2 = Frequency; static constexpr int Spi2 = Frequency; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f072rb/board.hpp b/src/modm/board/disco_f072rb/board.hpp index 60e7d69247..8d109ffc5c 100644 --- a/src/modm/board/disco_f072rb/board.hpp +++ b/src/modm/board/disco_f072rb/board.hpp @@ -59,6 +59,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f100rb/board.hpp b/src/modm/board/disco_f100rb/board.hpp index b84d3a314f..441e1042a8 100644 --- a/src/modm/board/disco_f100rb/board.hpp +++ b/src/modm/board/disco_f100rb/board.hpp @@ -67,6 +67,7 @@ struct SystemClock static constexpr uint32_t Timer15 = Apb2Timer; static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f303vc/board.hpp b/src/modm/board/disco_f303vc/board.hpp index 08b67f18d2..02ad1ef169 100644 --- a/src/modm/board/disco_f303vc/board.hpp +++ b/src/modm/board/disco_f303vc/board.hpp @@ -84,6 +84,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb2Timer; static constexpr uint32_t Usb = Ahb / 1.5; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f401vc/board.hpp b/src/modm/board/disco_f401vc/board.hpp index ac0d118e38..5f73233742 100644 --- a/src/modm/board/disco_f401vc/board.hpp +++ b/src/modm/board/disco_f401vc/board.hpp @@ -67,6 +67,7 @@ struct SystemClock static constexpr uint32_t Timer11 = Apb2Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index da703b7abf..5cefecd167 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -78,6 +78,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f429zi/board.hpp b/src/modm/board/disco_f429zi/board.hpp index 2a859ac41e..020b8c3008 100644 --- a/src/modm/board/disco_f429zi/board.hpp +++ b/src/modm/board/disco_f429zi/board.hpp @@ -79,6 +79,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f469ni/board.hpp.in b/src/modm/board/disco_f469ni/board.hpp.in index ba3163135b..c65d59c136 100644 --- a/src/modm/board/disco_f469ni/board.hpp.in +++ b/src/modm/board/disco_f469ni/board.hpp.in @@ -81,6 +81,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f746ng/board.hpp b/src/modm/board/disco_f746ng/board.hpp index ce09a92b12..1556ab94e2 100644 --- a/src/modm/board/disco_f746ng/board.hpp +++ b/src/modm/board/disco_f746ng/board.hpp @@ -80,6 +80,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_f769ni/board.hpp b/src/modm/board/disco_f769ni/board.hpp index 4fa836888e..81eb1d2039 100644 --- a/src/modm/board/disco_f769ni/board.hpp +++ b/src/modm/board/disco_f769ni/board.hpp @@ -80,6 +80,7 @@ struct SystemClock static constexpr uint32_t Timer12 = Apb1Timer; static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_l152rc/board.hpp b/src/modm/board/disco_l152rc/board.hpp index 0f115fcb0c..c405981635 100644 --- a/src/modm/board/disco_l152rc/board.hpp +++ b/src/modm/board/disco_l152rc/board.hpp @@ -57,6 +57,7 @@ struct SystemClock static constexpr uint32_t Timer9 = Apb2Timer; static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/disco_l476vg/board.hpp b/src/modm/board/disco_l476vg/board.hpp index f8175e63b6..c5ae9096b4 100644 --- a/src/modm/board/disco_l476vg/board.hpp +++ b/src/modm/board/disco_l476vg/board.hpp @@ -43,6 +43,7 @@ struct SystemClock static constexpr uint32_t Usart3 = Apb1; static constexpr uint32_t Usart4 = Apb1; static constexpr uint32_t Usart5 = Apb1; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f031k6/board.hpp b/src/modm/board/nucleo_f031k6/board.hpp index 5f61a9cc01..c2decbfe28 100644 --- a/src/modm/board/nucleo_f031k6/board.hpp +++ b/src/modm/board/nucleo_f031k6/board.hpp @@ -48,6 +48,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb; static constexpr uint32_t Timer16 = Apb; static constexpr uint32_t Timer17 = Apb; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f042k6/board.hpp b/src/modm/board/nucleo_f042k6/board.hpp index 893406afa8..09f8aa93c7 100644 --- a/src/modm/board/nucleo_f042k6/board.hpp +++ b/src/modm/board/nucleo_f042k6/board.hpp @@ -51,6 +51,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb; static constexpr uint32_t Timer16 = Apb; static constexpr uint32_t Timer17 = Apb; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f072rb/board.hpp b/src/modm/board/nucleo_f072rb/board.hpp index 5f240f1218..17b9c1d2d5 100644 --- a/src/modm/board/nucleo_f072rb/board.hpp +++ b/src/modm/board/nucleo_f072rb/board.hpp @@ -59,6 +59,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f091rc/board.hpp b/src/modm/board/nucleo_f091rc/board.hpp index 0a839d0b94..15a568e159 100644 --- a/src/modm/board/nucleo_f091rc/board.hpp +++ b/src/modm/board/nucleo_f091rc/board.hpp @@ -58,6 +58,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f103rb/board.hpp b/src/modm/board/nucleo_f103rb/board.hpp index 787be72758..7ad9c9db30 100644 --- a/src/modm/board/nucleo_f103rb/board.hpp +++ b/src/modm/board/nucleo_f103rb/board.hpp @@ -62,6 +62,7 @@ struct SystemClock static constexpr uint32_t Timer6 = Apb1Timer; static constexpr uint32_t Timer7 = Apb1Timer; static constexpr uint32_t Timer8 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f303k8/board.hpp b/src/modm/board/nucleo_f303k8/board.hpp index c1079d8c13..53c933c586 100644 --- a/src/modm/board/nucleo_f303k8/board.hpp +++ b/src/modm/board/nucleo_f303k8/board.hpp @@ -59,6 +59,7 @@ struct SystemClock { static constexpr uint32_t Timer15 = Apb2Timer; static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f303re/board.hpp b/src/modm/board/nucleo_f303re/board.hpp index bffe378a96..4fd12154b0 100644 --- a/src/modm/board/nucleo_f303re/board.hpp +++ b/src/modm/board/nucleo_f303re/board.hpp @@ -62,6 +62,7 @@ struct SystemClock static constexpr uint32_t Timer15 = Apb2Timer; static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f334r8/board.hpp b/src/modm/board/nucleo_f334r8/board.hpp index 2d41cce544..80fde9d9b2 100644 --- a/src/modm/board/nucleo_f334r8/board.hpp +++ b/src/modm/board/nucleo_f334r8/board.hpp @@ -59,6 +59,7 @@ struct SystemClock static constexpr uint32_t Timer15 = Apb2Timer; static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f401re/board.hpp b/src/modm/board/nucleo_f401re/board.hpp index 7f28db9805..e5188b7e64 100644 --- a/src/modm/board/nucleo_f401re/board.hpp +++ b/src/modm/board/nucleo_f401re/board.hpp @@ -60,6 +60,7 @@ struct SystemClock static constexpr uint32_t Timer9 = Apb2Timer; static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f411re/board.hpp b/src/modm/board/nucleo_f411re/board.hpp index 2f8553e456..5fff5998fb 100644 --- a/src/modm/board/nucleo_f411re/board.hpp +++ b/src/modm/board/nucleo_f411re/board.hpp @@ -62,6 +62,7 @@ struct SystemClock static constexpr uint32_t Timer9 = Apb2Timer; static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f429zi/board.hpp b/src/modm/board/nucleo_f429zi/board.hpp index 256003a372..7fbd76ab26 100644 --- a/src/modm/board/nucleo_f429zi/board.hpp +++ b/src/modm/board/nucleo_f429zi/board.hpp @@ -78,6 +78,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f446re/board.hpp b/src/modm/board/nucleo_f446re/board.hpp index b4bd50cf2e..783fbb4cb6 100644 --- a/src/modm/board/nucleo_f446re/board.hpp +++ b/src/modm/board/nucleo_f446re/board.hpp @@ -69,6 +69,7 @@ struct SystemClock static constexpr uint32_t Timer9 = Apb2Timer; static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f446ze/board.hpp b/src/modm/board/nucleo_f446ze/board.hpp index 2014a8a881..ed745af920 100644 --- a/src/modm/board/nucleo_f446ze/board.hpp +++ b/src/modm/board/nucleo_f446ze/board.hpp @@ -70,6 +70,7 @@ struct SystemClock static constexpr uint32_t Timer11 = Apb2Timer; static constexpr uint32_t Usb = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f746zg/board.hpp b/src/modm/board/nucleo_f746zg/board.hpp index a83d8e1fd0..4d2c366447 100755 --- a/src/modm/board/nucleo_f746zg/board.hpp +++ b/src/modm/board/nucleo_f746zg/board.hpp @@ -83,6 +83,7 @@ struct SystemClock static constexpr uint32_t Timer12 = Apb1Timer; static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_f767zi/board.hpp b/src/modm/board/nucleo_f767zi/board.hpp index 766aecedd4..9f02baa432 100755 --- a/src/modm/board/nucleo_f767zi/board.hpp +++ b/src/modm/board/nucleo_f767zi/board.hpp @@ -81,6 +81,7 @@ struct SystemClock static constexpr uint32_t Timer12 = Apb1Timer; static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_g071rb/board.hpp b/src/modm/board/nucleo_g071rb/board.hpp index c803d7227c..48406fed80 100644 --- a/src/modm/board/nucleo_g071rb/board.hpp +++ b/src/modm/board/nucleo_g071rb/board.hpp @@ -70,7 +70,7 @@ struct SystemClock static constexpr uint32_t Usart3 = Apb; static constexpr uint32_t Usart2 = Apb; static constexpr uint32_t Spi2 = Apb; - static constexpr uint32_t Iwdg = Apb; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static constexpr uint32_t Wwdg = Apb; static constexpr uint32_t Rtc = Apb; static constexpr uint32_t Timer14 = Apb; diff --git a/src/modm/board/nucleo_g431kb/board.hpp b/src/modm/board/nucleo_g431kb/board.hpp index b04e75bcf1..0b6b999c4d 100644 --- a/src/modm/board/nucleo_g431kb/board.hpp +++ b/src/modm/board/nucleo_g431kb/board.hpp @@ -84,6 +84,7 @@ struct SystemClock static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; static constexpr uint32_t Timer20 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_g431rb/board.hpp b/src/modm/board/nucleo_g431rb/board.hpp index fafd17fee7..de04fe9c12 100644 --- a/src/modm/board/nucleo_g431rb/board.hpp +++ b/src/modm/board/nucleo_g431rb/board.hpp @@ -79,6 +79,7 @@ struct SystemClock static constexpr uint32_t Timer15 = Apb2Timer; static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_g474re/board.hpp b/src/modm/board/nucleo_g474re/board.hpp index 870a8c178e..29d9ec86a5 100644 --- a/src/modm/board/nucleo_g474re/board.hpp +++ b/src/modm/board/nucleo_g474re/board.hpp @@ -92,6 +92,7 @@ struct SystemClock static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; static constexpr uint32_t Timer20 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_h723zg/board.hpp b/src/modm/board/nucleo_h723zg/board.hpp index 2b28ff010a..eb974e48cd 100644 --- a/src/modm/board/nucleo_h723zg/board.hpp +++ b/src/modm/board/nucleo_h723zg/board.hpp @@ -103,6 +103,7 @@ struct SystemClock static constexpr uint32_t Timer24 = Apb1Timer; static constexpr uint32_t Usb = 48_MHz; // From PLL3Q + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_h743zi/board.hpp b/src/modm/board/nucleo_h743zi/board.hpp index 9127839453..7847157a3c 100644 --- a/src/modm/board/nucleo_h743zi/board.hpp +++ b/src/modm/board/nucleo_h743zi/board.hpp @@ -96,6 +96,7 @@ struct SystemClock static constexpr uint32_t Timer17 = Apb2Timer; static constexpr uint32_t Usb = 48_MHz; // From PLL3Q + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l031k6/board.hpp b/src/modm/board/nucleo_l031k6/board.hpp index 0de709fb16..ce88228d8c 100644 --- a/src/modm/board/nucleo_l031k6/board.hpp +++ b/src/modm/board/nucleo_l031k6/board.hpp @@ -53,6 +53,7 @@ struct SystemClock static constexpr uint32_t Timer22 = Apb2Timer; static constexpr uint32_t Usart2 = Apb1; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l053r8/board.hpp b/src/modm/board/nucleo_l053r8/board.hpp index 27860ada47..ba8ca0d7c3 100644 --- a/src/modm/board/nucleo_l053r8/board.hpp +++ b/src/modm/board/nucleo_l053r8/board.hpp @@ -59,6 +59,7 @@ struct SystemClock static constexpr uint32_t Timer6 = Apb1Timer; static constexpr uint32_t Timer21 = Apb2Timer; static constexpr uint32_t Timer22 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l152re/board.hpp b/src/modm/board/nucleo_l152re/board.hpp index 8b89f6ee11..a33e6079e6 100644 --- a/src/modm/board/nucleo_l152re/board.hpp +++ b/src/modm/board/nucleo_l152re/board.hpp @@ -60,6 +60,7 @@ struct SystemClock static constexpr uint32_t Timer9 = Apb2Timer; static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l432kc/board.hpp b/src/modm/board/nucleo_l432kc/board.hpp index 87b3d860bb..3627d45d9b 100644 --- a/src/modm/board/nucleo_l432kc/board.hpp +++ b/src/modm/board/nucleo_l432kc/board.hpp @@ -43,6 +43,7 @@ struct SystemClock static constexpr uint32_t Spi1 = Apb2; static constexpr uint32_t Spi2 = Apb2; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l452re/board.hpp b/src/modm/board/nucleo_l452re/board.hpp index 27426c24e1..5b8d38e020 100644 --- a/src/modm/board/nucleo_l452re/board.hpp +++ b/src/modm/board/nucleo_l452re/board.hpp @@ -73,6 +73,7 @@ struct SystemClock static constexpr uint32_t Usart3 = Apb1; static constexpr uint32_t Usb = Apb1; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l476rg/board.hpp b/src/modm/board/nucleo_l476rg/board.hpp index a43fb0bcbc..2d887a5753 100644 --- a/src/modm/board/nucleo_l476rg/board.hpp +++ b/src/modm/board/nucleo_l476rg/board.hpp @@ -53,6 +53,7 @@ struct SystemClock static constexpr uint32_t Usart3 = Apb1; static constexpr uint32_t Usart4 = Apb1; static constexpr uint32_t Usart5 = Apb1; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l496zg-p/board.hpp b/src/modm/board/nucleo_l496zg-p/board.hpp index 0554d9c332..c0974a885a 100644 --- a/src/modm/board/nucleo_l496zg-p/board.hpp +++ b/src/modm/board/nucleo_l496zg-p/board.hpp @@ -81,6 +81,7 @@ struct SystemClock static constexpr uint32_t Usb = 48_MHz; static constexpr uint32_t Rng = 48_MHz; static constexpr uint32_t Sdmmc = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_l552ze-q/board.hpp b/src/modm/board/nucleo_l552ze-q/board.hpp index 6232761fd8..4b391492fa 100644 --- a/src/modm/board/nucleo_l552ze-q/board.hpp +++ b/src/modm/board/nucleo_l552ze-q/board.hpp @@ -82,6 +82,7 @@ struct SystemClock static constexpr uint32_t Usb = 48_MHz; static constexpr uint32_t Rng = 48_MHz; static constexpr uint32_t Sdmmc = 48_MHz; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/nucleo_u575zi-q/board.hpp b/src/modm/board/nucleo_u575zi-q/board.hpp index fbfd54c93a..2b4efeb31d 100644 --- a/src/modm/board/nucleo_u575zi-q/board.hpp +++ b/src/modm/board/nucleo_u575zi-q/board.hpp @@ -84,6 +84,7 @@ struct SystemClock static constexpr uint32_t Usb = Hsi48; static constexpr uint32_t Rng = Hsi48; static constexpr uint32_t Sdmmc = Pll1P; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/olimexino_stm32/board.hpp b/src/modm/board/olimexino_stm32/board.hpp index 4ce0cf8ed4..94a3d1a47f 100644 --- a/src/modm/board/olimexino_stm32/board.hpp +++ b/src/modm/board/olimexino_stm32/board.hpp @@ -62,6 +62,7 @@ struct SystemClock static constexpr uint32_t Timer6 = Apb1Timer; static constexpr uint32_t Timer7 = Apb1Timer; static constexpr uint32_t Timer8 = Apb2Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/stm32_f4ve/board.hpp b/src/modm/board/stm32_f4ve/board.hpp index 076b7e053d..12d99a479e 100644 --- a/src/modm/board/stm32_f4ve/board.hpp +++ b/src/modm/board/stm32_f4ve/board.hpp @@ -79,6 +79,7 @@ struct SystemClock static constexpr uint32_t Timer12 = Apb1Timer; static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() diff --git a/src/modm/board/stm32f030f4p6_demo/board.hpp b/src/modm/board/stm32f030f4p6_demo/board.hpp index 21c37300fa..e4c129201c 100644 --- a/src/modm/board/stm32f030f4p6_demo/board.hpp +++ b/src/modm/board/stm32f030f4p6_demo/board.hpp @@ -47,6 +47,7 @@ struct SystemClock static constexpr uint32_t Timer14 = Apb; static constexpr uint32_t Timer16 = Apb; static constexpr uint32_t Timer17 = Apb; + static constexpr uint32_t Iwdg = Rcc::LsiFrequency; static bool inline enable() From de86a149f50139043e123e0f8c6ed1dcc9c22d20 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Mon, 5 Feb 2024 21:20:29 +0100 Subject: [PATCH 121/159] [board] Deduplicate NUCLEO-G070rb board file --- src/modm/board/nucleo_g070rb/board.hpp | 158 ------------------------- src/modm/board/nucleo_g070rb/module.lb | 2 +- src/modm/board/nucleo_g071rb/board.hpp | 8 +- 3 files changed, 5 insertions(+), 163 deletions(-) delete mode 100644 src/modm/board/nucleo_g070rb/board.hpp diff --git a/src/modm/board/nucleo_g070rb/board.hpp b/src/modm/board/nucleo_g070rb/board.hpp deleted file mode 100644 index 91957c0e92..0000000000 --- a/src/modm/board/nucleo_g070rb/board.hpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2019, Niklas Hauser - * Copyright (c) 2023, Christopher Durand - * - * 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_STM32_NUCLEO_G070RB_HPP -#define MODM_STM32_NUCLEO_G070RB_HPP - -#include -#include -#include -/// @ingroup modm_board_nucleo_g070rb -#define MODM_BOARD_HAS_LOGGER - -using namespace modm::platform; - -namespace Board -{ -/// @ingroup modm_board_nucleo_g070rb -/// @{ -using namespace modm::literals; - -/// STM32G070RB running at 64MHz generated from the internal 16MHz oscillator -struct SystemClock -{ - static constexpr uint32_t Frequency = 64_MHz; - static constexpr uint32_t Ahb = Frequency; - static constexpr uint32_t Apb = Frequency; - - static constexpr uint32_t Rng = Ahb; - static constexpr uint32_t Crc = Ahb; - static constexpr uint32_t Flash = Ahb; - static constexpr uint32_t Exti = Ahb; - static constexpr uint32_t Rcc = Ahb; - static constexpr uint32_t Dmamux = Ahb; - static constexpr uint32_t Dma = Ahb; - - static constexpr uint32_t Dbg = Apb; - static constexpr uint32_t Timer17 = Apb; - static constexpr uint32_t Timer16 = Apb; - static constexpr uint32_t Timer15 = Apb; - static constexpr uint32_t Usart1 = Apb; - static constexpr uint32_t Spi1 = Apb; - static constexpr uint32_t I2s1 = Apb; - static constexpr uint32_t Timer1 = Apb; - static constexpr uint32_t Adc1 = Apb; - static constexpr uint32_t ItLine = Apb; - static constexpr uint32_t SysCfg = Apb; - static constexpr uint32_t Tamp = Apb; - static constexpr uint32_t Bkp = Apb; - static constexpr uint32_t Ucpd2 = Apb; - static constexpr uint32_t Ucpd1 = Apb; - static constexpr uint32_t Dac = Apb; - static constexpr uint32_t Pwr = Apb; - static constexpr uint32_t I2c2 = Apb; - static constexpr uint32_t I2c1 = Apb; - static constexpr uint32_t Usart4 = Apb; - static constexpr uint32_t Usart3 = Apb; - static constexpr uint32_t Usart2 = Apb; - static constexpr uint32_t Spi2 = Apb; - static constexpr uint32_t Iwdg = Apb; - static constexpr uint32_t Wwdg = Apb; - static constexpr uint32_t Rtc = Apb; - static constexpr uint32_t Timer14 = Apb; - static constexpr uint32_t Timer7 = Apb; - static constexpr uint32_t Timer6 = Apb; - static constexpr uint32_t Timer3 = Apb; - - static bool inline - enable() - { - Rcc::enableInternalClock(); // 16MHz - // (internal clock / 1_M) * 8_N / 2_R = 128MHz / 2 = 64MHz - const Rcc::PllFactors pllFactors{ - .pllM = 1, - .pllN = 8, - .pllR = 2, - }; - Rcc::enablePll(Rcc::PllSource::InternalClock, pllFactors); - Rcc::setFlashLatency(); - // switch system clock to PLL output - Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); - Rcc::setAhbPrescaler(Rcc::AhbPrescaler::Div1); - Rcc::setApbPrescaler(Rcc::ApbPrescaler::Div1); - // update frequencies for busy-wait delay functions - Rcc::updateCoreFrequency(); - - return true; - } -}; - -// Arduino Footprint -using A0 = GpioA0; -using A1 = GpioA1; -using A2 = GpioA4; -using A3 = GpioB1; -using A4 = GpioB11; -using A5 = GpioB12; - -using D0 = GpioC5; -using D1 = GpioC4; -using D2 = GpioA10; -using D3 = GpioB3; -using D4 = GpioB5; -using D5 = GpioB4; -using D6 = GpioB14; -using D7 = GpioA8; -using D8 = GpioA9; -using D9 = GpioC7; -using D10 = GpioB0; -using D11 = GpioA7; -using D12 = GpioA6; -using D13 = GpioA5; -using D14 = GpioB9; -using D15 = GpioB8; - -using Button = GpioInverted; -using LedD13 = D13; - -using Leds = SoftwareGpioPort< LedD13 >; -/// @} - -namespace stlink -{ -/// @ingroup modm_board_nucleo_g070rb -/// @{ -using Rx = GpioInputA3; -using Tx = GpioOutputA2; -using Uart = Usart2; -/// @} -} - -/// @ingroup modm_board_nucleo_g070rb -/// @{ -using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; - -inline void -initialize() -{ - SystemClock::enable(); - SysTickTimer::initialize(); - - stlink::Uart::connect(); - stlink::Uart::initialize(); - - Button::setInput(); -} -/// @} - -} - -#endif // MODM_STM32_NUCLEO_G070RB_HPP diff --git a/src/modm/board/nucleo_g070rb/module.lb b/src/modm/board/nucleo_g070rb/module.lb index 148f459ada..147168f08b 100644 --- a/src/modm/board/nucleo_g070rb/module.lb +++ b/src/modm/board/nucleo_g070rb/module.lb @@ -34,7 +34,7 @@ def build(env): "with_assert": env.has_module(":architecture:assert") } env.template("../board.cpp.in", "board.cpp") - env.copy('.') + env.copy("../nucleo_g071rb/board.hpp", "board.hpp") env.outbasepath = "modm/openocd/modm/board/" env.copy(repopath("tools/openocd/modm/st_nucleo_g0.cfg"), "st_nucleo_g0.cfg") diff --git a/src/modm/board/nucleo_g071rb/board.hpp b/src/modm/board/nucleo_g071rb/board.hpp index 48406fed80..59c91861ca 100644 --- a/src/modm/board/nucleo_g071rb/board.hpp +++ b/src/modm/board/nucleo_g071rb/board.hpp @@ -14,14 +14,14 @@ #include #include #include -/// @ingroup modm_board_nucleo_g071rb +/// @ingroup modm_board_nucleo_g071rb modm_board_nucleo_g070rb #define MODM_BOARD_HAS_LOGGER using namespace modm::platform; namespace Board { -/// @ingroup modm_board_nucleo_g071rb +/// @ingroup modm_board_nucleo_g071rb modm_board_nucleo_g070rb /// @{ using namespace modm::literals; @@ -135,7 +135,7 @@ using Leds = SoftwareGpioPort< LedD13 >; namespace stlink { -/// @ingroup modm_board_nucleo_g071rb +/// @ingroup modm_board_nucleo_g071rb modm_board_nucleo_g070rb /// @{ using Rx = GpioInputA3; using Tx = GpioOutputA2; @@ -143,7 +143,7 @@ using Uart = Usart2; /// @} } -/// @ingroup modm_board_nucleo_g071rb +/// @ingroup modm_board_nucleo_g071rb modm_board_nucleo_g070rb /// @{ using LoggerDevice = modm::IODeviceWrapper< stlink::Uart, modm::IOBuffer::BlockIfFull >; From 9587afce127b40644c23a513494b022866528b2e Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 28 Jan 2024 01:02:29 +0100 Subject: [PATCH 122/159] [uart] STM32 12-bit prescaler must not be zero --- examples/nucleo_l432kc/gyroscope/main.cpp | 2 +- src/modm/platform/uart/stm32/uart_hal_impl.hpp.in | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nucleo_l432kc/gyroscope/main.cpp b/examples/nucleo_l432kc/gyroscope/main.cpp index 5180cc25dd..f9fb23cb13 100644 --- a/examples/nucleo_l432kc/gyroscope/main.cpp +++ b/examples/nucleo_l432kc/gyroscope/main.cpp @@ -95,7 +95,7 @@ main() Board::initialize(); UartSpi::Master::connect(); - UartSpi::Master::initialize(); + UartSpi::Master::initialize(); while (true) { reader.update(); diff --git a/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in b/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in index f24d5e9a88..3232d73b2b 100644 --- a/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in +++ b/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in @@ -70,7 +70,7 @@ void %% if over8 constexpr uint32_t scalar = (baudrate * 16 > SystemClock::{{ name }}) ? 8 : 16; constexpr uint32_t max = ((scalar == 16) ? (1ul << 16) : (1ul << 15)) - 1ul; - constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, 1, max); + constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, scalar, max); %% elif uart_type == "Lpuart" static_assert(SystemClock::{{ name }} >= baudrate * 3, "fck must be in the range [3 x baud rate, 4096 x baud rate]."); @@ -83,7 +83,7 @@ void 0x300, max); %% else constexpr uint32_t max = (1ul << 16) - 1ul; - constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, 1, max); + constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, 16, max); %% endif modm::PeripheralDriver::assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); From 1fb1339ae9958afb51c5a8645178da1cea84c214 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 6 Feb 2024 20:09:52 +0100 Subject: [PATCH 123/159] [math] Fix enumerate() when range reference type is not a reference --- src/modm/math/algorithm/enumerate.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modm/math/algorithm/enumerate.hpp b/src/modm/math/algorithm/enumerate.hpp index 883ef1884d..39c27c5d8b 100644 --- a/src/modm/math/algorithm/enumerate.hpp +++ b/src/modm/math/algorithm/enumerate.hpp @@ -12,8 +12,10 @@ #pragma once -#include +#include +#include #include +#include namespace modm { @@ -31,7 +33,10 @@ constexpr auto enumerate(T && iterable) TIter iter; constexpr bool operator != (const iterator & other) const { return iter != other.iter; } constexpr void operator ++ () { ++i; ++iter; } - constexpr auto operator * () const { return std::tie(i, *iter); } + constexpr auto operator * () const + { + return std::tuple>{i, *iter}; + } }; struct iterable_wrapper { From 3811b71298a46a0d9f8ba2afa950a4e17b7e6f45 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 27 Jan 2024 17:19:48 +0100 Subject: [PATCH 124/159] [math] Remove Tolerance class in favor of function --- .../architecture/interface/peripheral.hpp | 2 +- src/modm/math/tolerance.hpp | 58 +++---------------- src/modm/platform/usb/sam/usb.hpp.in | 2 +- test/modm/ui/color/color_test.cpp | 14 ++--- 4 files changed, 18 insertions(+), 58 deletions(-) diff --git a/src/modm/architecture/interface/peripheral.hpp b/src/modm/architecture/interface/peripheral.hpp index 7277c9291e..4cdf4fb1f6 100644 --- a/src/modm/architecture/interface/peripheral.hpp +++ b/src/modm/architecture/interface/peripheral.hpp @@ -88,7 +88,7 @@ class PeripheralDriver static void assertBaudrateInTolerance() { - static_assert(modm::Tolerance::isValueInTolerance(requested, available, tolerance), + static_assert(modm::isValueInTolerance(requested, available, tolerance), "The closest available baudrate exceeds the tolerance of the requested baudrate!"); } }; diff --git a/src/modm/math/tolerance.hpp b/src/modm/math/tolerance.hpp index f9a05fa5a0..d8f9d76b7a 100644 --- a/src/modm/math/tolerance.hpp +++ b/src/modm/math/tolerance.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015, 2018, Niklas Hauser + * Copyright (c) 2013-2015, 2018, 2024, Niklas Hauser * * This file is part of the modm project. * @@ -9,60 +9,20 @@ */ // ---------------------------------------------------------------------------- -#ifndef MODM_MATH_TOLERANCE_HPP -#define MODM_MATH_TOLERANCE_HPP +#pragma once -#include #include +#include namespace modm { -/** - * This class checks if values are within a certain tolerance. - * - * This can be used to guarantee the quality of certain parameters, - * mostly baudrate or datarate. - * - * @ingroup modm_math - */ -class -Tolerance +/// @ingroup modm_math +template< typename T > +constexpr bool +isValueInTolerance(T reference, T actual, percent_t tolerance) { -public: - static constexpr int64_t - absoluteError(uint32_t reference, uint32_t actual) - { - return (int64_t(reference) - actual); - } - - static constexpr float - relativeError(uint32_t reference, uint32_t actual) - { - return (1.f - float(actual) / reference); - } - - static constexpr bool - isErrorInTolerance(float error, percent_t tolerance) - { - return (error == 0) or (((error > 0) ? error : -error) <= pct2f(tolerance)); - } - - static constexpr bool - isValueInTolerance(uint32_t reference, uint32_t actual, percent_t tolerance) - { - return isErrorInTolerance(relativeError(reference, actual), tolerance); - } - - template< uint32_t reference, uint32_t actual, percent_t tolerance > - static void - assertValueInTolerance() - { - static_assert(isValueInTolerance(reference, actual, tolerance), - "The actual value exceeds the tolerance of reference!"); - } -}; + return std::abs(1.0 - double(actual) / double(reference)) <= std::abs(double(tolerance)); +} } // namespace modm - -#endif // MODM_MATH_TOLERANCE_HPP diff --git a/src/modm/platform/usb/sam/usb.hpp.in b/src/modm/platform/usb/sam/usb.hpp.in index 5db79081a8..dd37c4e266 100644 --- a/src/modm/platform/usb/sam/usb.hpp.in +++ b/src/modm/platform/usb/sam/usb.hpp.in @@ -30,7 +30,7 @@ public: { %% if target["family"] == "g5x" // only the USB device mode is supported for now - static_assert(modm::Tolerance::isValueInTolerance(48_MHz, SystemClock::Usb, 0.25_pct), + static_assert(modm::isValueInTolerance(48_MHz, SystemClock::Usb, 0.25_pct), "The USB clock frequency must be within 0.25\% of 48MHz!"); // Select DM/DP function for PA21/22 MATRIX->CCFG_SYSIO &= ~(CCFG_SYSIO_SYSIO11 | CCFG_SYSIO_SYSIO10); diff --git a/test/modm/ui/color/color_test.cpp b/test/modm/ui/color/color_test.cpp index ce4aca9a24..0864e30282 100644 --- a/test/modm/ui/color/color_test.cpp +++ b/test/modm/ui/color/color_test.cpp @@ -93,9 +93,9 @@ void ColorTest::testRgbHsvPingPongConvertion_8bit() // Convertion can distort - allow some tolerance. using namespace modm; - TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb8.red, rgb8_b.red, 1_pct)); - TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb8.green, rgb8_b.green, 1_pct)); - TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb8.blue, rgb8_b.blue, 1_pct)); + TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb8.red, rgb8_b.red, 1_pct)); + TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb8.green, rgb8_b.green, 1_pct)); + TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb8.blue, rgb8_b.blue, 1_pct)); } // TODO 16bit convertion not yet working @@ -109,7 +109,7 @@ void ColorTest::testRgbHsvPingPongConvertion_8bit() // // Convertion can distort - allow some tolerance. // using namespace modm; -// TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb.red, rgb16_b.red, 1_pct)); -// TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb.green, rgb16_b.green, 1_pct)); -// TEST_ASSERT_TRUE(modm::Tolerance::isValueInTolerance(rgb.blue, rgb16_b.blue, 1_pct)); -// } \ No newline at end of file +// TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb.red, rgb16_b.red, 1_pct)); +// TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb.green, rgb16_b.green, 1_pct)); +// TEST_ASSERT_TRUE(modm::isValueInTolerance(rgb.blue, rgb16_b.blue, 1_pct)); +// } From 8f880eed02341d87234f38a0f0f020a6d0042928 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 26 Jan 2024 22:22:17 +0100 Subject: [PATCH 125/159] [math] percent_t unit can be float since C++20 --- src/modm/math/units.hpp | 26 ++++++++++--------- src/modm/math/units.lb | 15 ++++------- .../platform/can/common/can_bit_timings.hpp | 2 +- .../platform/i2c/sam_x7x/i2c_master.hpp.in | 2 +- .../stm32-extended/i2c_timing_calculator.hpp | 4 +-- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/modm/math/units.hpp b/src/modm/math/units.hpp index 564e4878a6..0ba568a623 100644 --- a/src/modm/math/units.hpp +++ b/src/modm/math/units.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Niklas Hauser + * Copyright (c) 2019, 2024, Niklas Hauser * * This file is part of the modm project. * @@ -36,7 +36,7 @@ namespace modm template constexpr bitrate_t kbps(T value); template constexpr bitrate_t Mbps(T value); - using percent_t = uint16_t; + using percent_t = float; template constexpr percent_t pct(T value); /// @} @@ -63,8 +63,7 @@ namespace modm #else -#define MODM_LITERAL_DEFINITION(type, name, symbol) \ -namespace modm { \ +#define MODM_UNITS_LITERAL_DEFINITION(type, name, symbol) \ using MODM_CONCAT(name, _t) = type; \ template constexpr MODM_CONCAT(name, _t) symbol(T value) { return value; } \ template constexpr MODM_CONCAT(name, _t) MODM_CONCAT(k, symbol)(T value) { return value * 1'000ul; } \ @@ -75,16 +74,19 @@ namespace literals { \ constexpr MODM_CONCAT(name, _t) MODM_CONCAT(operator""_k, symbol)(long double value) { return MODM_CONCAT(k, symbol)(value); } \ constexpr MODM_CONCAT(name, _t) MODM_CONCAT(operator""_M, symbol)(unsigned long long int value) { return MODM_CONCAT(M, symbol)(value); } \ constexpr MODM_CONCAT(name, _t) MODM_CONCAT(operator""_M, symbol)(long double value) { return MODM_CONCAT(M, symbol)(value); } \ -}} +} + +namespace modm +{ -MODM_LITERAL_DEFINITION(uint32_t, frequency, Hz) -MODM_LITERAL_DEFINITION(uint32_t, baudrate, Bd) -MODM_LITERAL_DEFINITION(uint32_t, bitrate, bps) +MODM_UNITS_LITERAL_DEFINITION(uint32_t, frequency, Hz) +MODM_UNITS_LITERAL_DEFINITION(uint32_t, baudrate, Bd) +MODM_UNITS_LITERAL_DEFINITION(uint32_t, bitrate, bps) -namespace modm { -enum class percent_t : uint16_t {}; -template constexpr percent_t pct(T value) { return percent_t(uint16_t(value * 600ul)); } -constexpr float pct2f(percent_t value) { return uint16_t(value) / 60'000.f; } +using percent_t = float; +template constexpr percent_t pct(T value) { return value / 100.f; } +// DEPRECATED: 2025q1 +modm_deprecated("Access the value directly.") constexpr float pct2f(percent_t value) { return value; } namespace literals { constexpr percent_t operator""_pct(long double value) { return pct(value); } diff --git a/src/modm/math/units.lb b/src/modm/math/units.lb index 12250bb851..0ff3712f15 100644 --- a/src/modm/math/units.lb +++ b/src/modm/math/units.lb @@ -45,25 +45,20 @@ bitrate_t bitrate = modm::kbps(125); frequency = 4295_MHz; // OVERFLOW at 2^32 units! ``` -## Integral Percentages -Since `float` cannot be used as a non-type template argument, an integer type -for providing tolerances in `percent_t` is available. -Note that `percent_t` is implemented as an enum class, which prevents implicit -conversions, since the base for this is not 1. -You must therefore use the `modm::pct(T value)` or `_pct` constructors. +## Percentages + +Percentages are stored as normalized floating point numbers and can be converted +using these convenience constructors: ```cpp using namespace modm::literals; percent_t tolerance = modm::pct(10); tolerance = 10_pct; - -// convert back to float. *internal use only* -float percent = modm::pct2f(tolerance); + tolerance = 0.1f; ``` -!!! warning "This type is not guaranteed to hold more than 100 percent!" """ diff --git a/src/modm/platform/can/common/can_bit_timings.hpp b/src/modm/platform/can/common/can_bit_timings.hpp index 57cfcc8dd7..247f6c9557 100644 --- a/src/modm/platform/can/common/can_bit_timings.hpp +++ b/src/modm/platform/can/common/can_bit_timings.hpp @@ -115,7 +115,7 @@ class CanBitTiming template static constexpr void assertBitrateInTolerance() { - static_assert(pct2f(tolerance) >= BestConfig.error, + static_assert(tolerance >= BestConfig.error, "The closest available bitrate exceeds the specified maximum tolerance!"); } diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in index f62d0b34b0..1b881334de 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in @@ -61,7 +61,7 @@ private: const float freq = 1.f / (tHigh + tLow); const float error = std::fabs(1.f - freq / baudrate); - return error < pct2f(tolerance); + return error < tolerance; } template diff --git a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp index c80cfd69a1..be9bac7870 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp +++ b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp @@ -97,7 +97,7 @@ class I2cTimingCalculator float speed = calculateSpeed(prescaler, sclLow, sclHigh); auto error = std::fabs((speed / params.targetSpeed) - 1.0f); // modm::Tolerance value is in unit [1/1000] - if ((error < pct2f(params.tolerance)) && (error <= lastError) && (prescaler <= bestPrescaler)) { + if ((error < params.tolerance) && (error <= lastError) && (prescaler <= bestPrescaler)) { lastError = error; bestPrescaler = prescaler; bestSclLow = sclLow; @@ -290,7 +290,7 @@ class I2cTimingCalculator auto clockPeriod = float(prescaler + 1) / params.peripheralClock; auto targetSclTime = 1.f / params.targetSpeed; - auto sclTimeMax = targetSclTime * (1.f + pct2f(params.tolerance)); + auto sclTimeMax = targetSclTime * (1.f + params.tolerance); auto maxSclSum = ((sclTimeMax - params.riseTime - params.fallTime - 2*SyncTime) / clockPeriod) - 2; From 534be224608717ecad910cdba98c31dcf0584bde Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 26 Jan 2024 22:35:18 +0100 Subject: [PATCH 126/159] [math] Add chrono units for template parameters --- src/modm/math/units.hpp | 19 +++++++++++++++++++ src/modm/math/units.lb | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/modm/math/units.hpp b/src/modm/math/units.hpp index 0ba568a623..fc68df8c03 100644 --- a/src/modm/math/units.hpp +++ b/src/modm/math/units.hpp @@ -14,6 +14,7 @@ #include #include +#include #ifdef __DOXYGEN__ @@ -38,6 +39,10 @@ namespace modm using percent_t = float; template constexpr percent_t pct(T value); + + using seconds_t = std::chrono::seconds; + using milliseconds_t = std::chrono::milliseconds; + using microseconds_t = std::chrono::microseconds; /// @} namespace literals @@ -76,6 +81,15 @@ namespace literals { \ constexpr MODM_CONCAT(name, _t) MODM_CONCAT(operator""_M, symbol)(long double value) { return MODM_CONCAT(M, symbol)(value); } \ } +#define MODM_UNITS_TIME_DEFINITION(type) \ +struct MODM_CONCAT(type, _t) { \ + template< class Rep, class Period > \ + constexpr MODM_CONCAT(type, _t)(std::chrono::duration duration) \ + : count_{std::chrono::duration_cast(duration).count()} {} \ + std::chrono::type::rep count_; \ + constexpr std::chrono::type::rep count() const { return count_; } \ +}; + namespace modm { @@ -92,6 +106,11 @@ namespace literals constexpr percent_t operator""_pct(long double value) { return pct(value); } constexpr percent_t operator""_pct(unsigned long long int value) { return pct(value); } } + +MODM_UNITS_TIME_DEFINITION(seconds) +MODM_UNITS_TIME_DEFINITION(milliseconds) +MODM_UNITS_TIME_DEFINITION(microseconds) + using namespace ::modm::literals; } diff --git a/src/modm/math/units.lb b/src/modm/math/units.lb index 0ff3712f15..35bae20e26 100644 --- a/src/modm/math/units.lb +++ b/src/modm/math/units.lb @@ -60,6 +60,24 @@ percent_t tolerance = modm::pct(10); ``` +## Time + +Simplified types allow passing `std::chrono::duration` values as template +parameters: + +```cpp +seconds_t duration = 10s; +milliseconds_t duration = 10ms; +microseconds_t duration = 10us; + +auto count = duration.count(); + +template< milliseconds_t period > +void setPeriod() +{ + auto seconds = 1000.0 / period.count(); +} +``` """ def prepare(module, options): From fdd2a9cfa118cc7903f7a382e65bdbd282a753f5 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 21 Jan 2024 23:15:28 +0100 Subject: [PATCH 127/159] [math] Add prescaler + counter algorithm --- src/modm/math/algorithm/module.lb | 2 +- src/modm/math/algorithm/module.md | 196 ++++++++++++++++ src/modm/math/algorithm/prescaler.hpp | 103 ++++----- src/modm/math/algorithm/prescaler_counter.hpp | 183 +++++++++++++++ .../clock/systick/systick_timer.hpp.in | 4 +- src/modm/platform/i2c/stm32/i2c_master.hpp.in | 2 +- src/modm/platform/spi/rp/spi_master.hpp.in | 2 +- src/modm/platform/spi/sam/spi_master.hpp.in | 2 +- .../platform/spi/sam_x7x/spi_master.hpp.in | 2 +- .../platform/uart/at90_tiny_mega/uart.hpp.in | 2 +- src/modm/platform/uart/rp/uart.hpp.in | 2 +- .../uart/sam-sercom/uart_hal_impl.hpp.in | 2 +- .../platform/uart/stm32/uart_hal_impl.hpp.in | 6 +- .../math/algorithm/prescaler_counter_test.cpp | 212 ++++++++++++++++++ .../math/algorithm/prescaler_counter_test.hpp | 29 +++ test/modm/math/algorithm/prescaler_test.cpp | 12 +- test/modm/math/algorithm/prescaler_test.hpp | 2 +- 17 files changed, 693 insertions(+), 70 deletions(-) create mode 100644 src/modm/math/algorithm/module.md create mode 100644 src/modm/math/algorithm/prescaler_counter.hpp create mode 100644 test/modm/math/algorithm/prescaler_counter_test.cpp create mode 100644 test/modm/math/algorithm/prescaler_counter_test.hpp diff --git a/src/modm/math/algorithm/module.lb b/src/modm/math/algorithm/module.lb index 5e4b1b51d2..401d562a15 100644 --- a/src/modm/math/algorithm/module.lb +++ b/src/modm/math/algorithm/module.lb @@ -13,7 +13,7 @@ def init(module): module.name = ":math:algorithm" - module.description = "Math Algorithms" + module.description = FileReader("module.md") def prepare(module, options): module.depends(":math:units") diff --git a/src/modm/math/algorithm/module.md b/src/modm/math/algorithm/module.md new file mode 100644 index 0000000000..98809a438d --- /dev/null +++ b/src/modm/math/algorithm/module.md @@ -0,0 +1,196 @@ +# Algorithms + +A collection of useful and lightweight algorithms. + + +## Convenience Iterators + +Inspired by Python's built-in `range` and `enumerate` functions, you can use +`modm::range` and `modm::enumerate` in for loops: + +```cpp +// Iterates over 0 .. 9 +for (auto i : modm::range(10)) { + MODM_LOG_INFO << i << modm::endl; +} +// Iterates over 10 .. 19 +for (auto i : modm::range(10, 20)) { + MODM_LOG_INFO << i << modm::endl; +} +// Iterates over 20, 22, 24, 26, 28 +for (auto i : modm::range(20, 30, 2)) { + MODM_LOG_INFO << i << modm::endl; +} + +// Iterates over 0 .. N-1, where N = size of iterable +for (auto [i, item] : modm::enumerate(iterable)) { + MODM_LOG_INFO << i << item << modm::endl; +} +``` + + +## Prescaler Calculators + +Peripheral output frequencies are usually generated by dividing an input clock +with a prescaler in hardware. Finding the closest prescaler value for a desired +output frequency can be unintuitive, therefore, these classes provide a simple +interface for a constexpr calculator. + +All calculators return a `Result` struct containing desired, input, and output +frequencies, the relative error of the output vs desired frequency, and the +prescaler and its index. The prescaler index is typically the value to write +to the register directly: + +```cpp +// 16-bit linear prescaler [1, 2^16] mapped as [0, 0xffff]. +constexpr auto result = Prescaler::from_linear(10_MHz, 1_kHz, 1, 1ul << 16); +static_assert(result.input_frequency == 10_MHz); +static_assert(result.desired_frequency == 1_kHz); +// Calculator finds an exact match without error +static_assert(result.frequency == 1_kHz); +static_assert(result.error == 0); +// with prescaler 1e4 = 1e7 / 1e3. +static_assert(result.prescaler == 10'000); +static_assert(result.index == 9'999); +PERIPHERAL->PRESCALER = result.index; +``` + +The index is particularly useful for non-contiguous prescalers, the most common +being power-of-two values: + +```cpp +// Power-of-two prescaler with 8 values between [16, 4096]. +constexpr auto result = Prescaler::from_power(32_kHz, 100_Hz, 1ul << 4, 1ul << 12); +// Calculator cannot find an exact match! Closest has -25% error! +static_assert(result.frequency == 125_Hz); +static_assert(result.error == -0.25); +// Ideal Prescaler is 320, clostest is 256 +static_assert(result.prescaler == 256); +// Index is 256 = 1ul << (4 + 4) +static_assert(result.index == 4); +``` + +Non-contiguous prescalers can also be created with a modifier function: + +```cpp +// Only even prescalers from [2, 1024] +constexpr auto result = Prescaler::from_function( + 110_MHz, 3.5_MHz, 1, 512, [](uint32_t i){ return i*2; }); +// Ideal prescaler is 31.4, closest is 32 with ~2% error. +static_assert(result.frequency == 3.4375_MHz); +static_assert(result.error == 0.02); +static_assert(result.prescaler == 32); +static_assert(result.index == 15); // 32/2 - 1 +``` + +For all other cases, prescalers can be passed as an initializer list or as any +forward range. Note that the prescaler values *must* be sorted, otherwise +the calculator will compute the wrong prescaler values! + +```cpp +constexpr auto result = Prescaler::from_list(1_MHz, 1_kHz, {2,4,16,256,1024}); +constexpr auto result = Prescaler::from_range(2_kHz, 1_kHz, std::array{1,2,3}); +``` + +A special case is made of two chained prescalers that are both linear +powers-of-two. These are often called "fractional prescalers" and act as a +single binary-scaled linear prescaler and can thus be modeled as such: + +```cpp +// A fractional 12.4-bit prescaler can be modeled as a single 16-bit prescaler. +constexpr auto result = Prescaler::from_linear(SystemClock::Usart1, 115200, 16, 1ul << 16); +// The resulting prescaler can be written directly to the register. +USART1->BRR = result.prescaler; +``` + + +### Prescalers with Counters + +However, often chained prescalers cannot be converted to a linear prescaler, for +example, a timer with a set of power-of-two prescalers and a 16 or 32-bit +counter. These must be computed with a different class: + +```cpp +// A prescaler with power-of-two values [4, 256] going into a 12-bit down counter. +constexpr auto result = PrescalerCounter::from_power(32_kHz, 1_Hz, 1ul << 12, 4, 256); +// Calculator finds an exact match without error +static_assert(result.frequency == 1_Hz); +static_assert(result.error == 0); +// with prescaler 8 and counter 4'000. +static_assert(result.prescaler == 8); +static_assert(result.counter == 4'000); +static_assert(result.index == 1); +``` + +The calculator only picks the first prescaler with the lowest error, however, +in this example, there can be multiple exact solutions: + + 32000 = 8 × 4000 = 16 × 2000 = ... = 128 × 250 = 256 × 125 + +If the prescaler and counter is used to generate a waveform like PWM, then it is +beneficial to pick the combination with the largest counter value. However, if +the use case is to preserve power, then a slow running counter requires the +highest prescaler. Therefore the order of prescalers can be reversed: + +```cpp +constexpr auto result = PrescalerCounter::from_power(32_kHz, 1_Hz, 1ul << 12, 256, 4); +static_assert(result.prescaler == 256); +static_assert(result.counter == 125); +// Index is not the same! +static_assert(result.index == 0); +``` + +The same applies to the `PrescalerCounter::from_linear()` and +`PrescalerCounter::from_function()` calculators, while the order for lists and +forward ranges can be entirely arbitrary: + +```cpp +constexpr auto result = PrescalerCounter::from_list(32_kHz, 1_Hz, 1ul << 12, {128,16,256,4}); +static_assert(result.prescaler == 128); +static_assert(result.counter == 250); +// Index is relative to the list order now! +static_assert(result.index == 0); +``` + +!!! tip "Time Durations" + While the calculator is designed for frequencies, time durations can also be + computed by transforming the input as `frequency = 1.0 / duration` and then + transforming the output back as `duration = 1.0 / frequency`. + +!!! success "Floating-Point Frequencies" + You can define the type used for frequency representation by using the + `GenericPrescaler` and `GenericPrescalerCounter` classes. + + +### Tolerance of Prescaler Error + +Each `Result` has a signed(!), relative error attached, which can be used to +assert on the quality of the calculation. Note that using `static_assert` on the +error directly will only print the error values: + +```cpp +static_assert(std::abs(result.error) < 5_pct); +``` +``` +error: static assertion failed + | static_assert(std::abs(result.error) < tolerance); + | ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~ +note: the comparison reduces to '(0.10 < 0.05)' +``` + +However, by using a helper method, the requested and closest available +frequencies can be displayed to the developer: + +```cpp +// Accidentally used kBaud instead of Baud, which cannot be generated +constexpr auto result = Prescaler::from_linear(SystemClock::Usart2, 115200_kBd, 16, 1ul << 16); +modm::assertBaudrateInTolerance(); +``` +``` +In instantiation of 'static void modm::PeripheralDriver::assertBaudrateInTolerance() +[with long long unsigned int available = 3000000; long long unsigned int requested = 115200000; float tolerance = 0.01f]': +error: static assertion failed: The closest available baudrate exceeds the tolerance of the requested baudrate! + | static_assert(modm::isValueInTolerance(requested, available, tolerance), + | ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +note: 'modm::isValueInTolerance(115200000, 3000000, 0.01f)' evaluates to false +``` diff --git a/src/modm/math/algorithm/prescaler.hpp b/src/modm/math/algorithm/prescaler.hpp index 502d2d7f25..4d8ca958e7 100644 --- a/src/modm/math/algorithm/prescaler.hpp +++ b/src/modm/math/algorithm/prescaler.hpp @@ -15,10 +15,8 @@ #include #include -#include -#include #include -#include +#include namespace modm { @@ -29,50 +27,49 @@ namespace modm * @note For ranges the end is *inclusive*: [begin, end]! * @ingroup modm_math_algorithm */ -template +template class GenericPrescaler { public: struct Result { - constexpr Result(T input_frequency, - T desired_frequency, - uint32_t index, uint32_t prescaler) : + constexpr Result(T input_frequency, T desired_frequency, + uint32_t index, uint32_t prescaler) : index{index}, prescaler{prescaler}, frequency{input_frequency / prescaler}, input_frequency{input_frequency}, desired_frequency{desired_frequency}, - error{1.f - float(input_frequency / prescaler) / desired_frequency} + error{1.f - float(input_frequency) / float(prescaler * desired_frequency)} {} /// Zero-indexed prescaler result - const uint32_t index; + uint32_t index; /// Prescaler value - const uint32_t prescaler; + uint32_t prescaler; /// Generated frequency - const T frequency; + T frequency; /// Input frequency - const T input_frequency; + T input_frequency; /// Desired Frequency - const T desired_frequency; + T desired_frequency; /// Relative Frequency Error - const float error; + float error; }; public: - /// From any iterable container. - /// @note container must have `begin()`, `end()` and `size()` function! - template + /// From any forward range. + /// @pre The container must be sorted from low to high numbers! static constexpr Result - from_iterator(T input_frequency, - T desired_frequency, - Iterator prescalers) + from_range(T input_frequency, T desired_frequency, + std::ranges::forward_range auto&& prescalers) { const double desired = double(input_frequency) / desired_frequency; - uint32_t prescaler_floor{*prescalers.begin()}; - uint32_t prescaler_ceiling{*prescalers.begin()}; + const size_t prescaler_size = std::ranges::distance( + std::ranges::begin(prescalers), std::ranges::end(prescalers)); + uint32_t prescaler_floor{*std::ranges::begin(prescalers)}; + uint32_t prescaler_ceiling{*std::ranges::begin(prescalers)}; uint32_t ii_floor{0}, ii_ceiling{0}; - if (desired <= prescaler_floor or prescalers.size() == 1) + if (desired <= prescaler_floor or prescaler_size == 1) return {input_frequency, desired_frequency, 0, prescaler_floor}; for (uint32_t prescaler : prescalers) { @@ -91,54 +88,62 @@ GenericPrescaler } /// From a initializer list. + /// @pre The list must be sorted from low to high numbers! static constexpr Result - from_list(T input_frequency, - T desired_frequency, + from_list(T input_frequency, T desired_frequency, std::initializer_list prescalers) { - return from_iterator(input_frequency, desired_frequency, prescalers); + return from_range(input_frequency, desired_frequency, prescalers); } /// From any linear range via modifier function. /// @note the range end is *inclusive*: [begin, end]. + /// @pre begin must be smaller or equal than end! template< typename Function > static constexpr Result - from_function(T input_frequency, - T desired_frequency, - uint32_t begin, uint32_t end, - Function prescaler_modifier) + from_function(T input_frequency, T desired_frequency, + uint32_t begin, uint32_t end, Function prescaler_modifier) { struct linear_range_iterator { + using iterator_category [[maybe_unused]] = std::forward_iterator_tag; + using value_type [[maybe_unused]] = uint32_t; + using difference_type [[maybe_unused]] = int32_t; + using pointer [[maybe_unused]] = value_type*; + using reference [[maybe_unused]] = value_type&; + uint32_t state; - const Function modifier; - constexpr uint32_t operator*() const { return modifier(state); } + Function *modifier; + + constexpr value_type operator*() const { return (*modifier)(state); } constexpr linear_range_iterator& operator++() { state++; return *this; } - constexpr bool operator!=(const linear_range_iterator &o) const { return state != o.state; } + constexpr linear_range_iterator operator++(int) { auto old(*this); ++*this; return old; } + constexpr bool operator==(const linear_range_iterator &o) const { return state == o.state; } + constexpr difference_type operator-(const linear_range_iterator &o) const { return state - o.state; } }; struct linear_range { - const uint32_t m_begin; - const uint32_t m_end; - const Function modifier; - constexpr linear_range_iterator begin() const { return linear_range_iterator{m_begin, modifier}; } - constexpr linear_range_iterator end() const { return linear_range_iterator{m_end, modifier}; } + uint32_t m_begin; + uint32_t m_end; + Function *modifier; + + constexpr linear_range_iterator begin() const { return {m_begin, modifier}; } + constexpr linear_range_iterator end() const { return {m_end, modifier}; } constexpr size_t size() const { return m_end - m_begin; } }; - linear_range range{begin, end+1, prescaler_modifier}; - return from_iterator(input_frequency, desired_frequency, range); + linear_range range{begin, end+1, &prescaler_modifier}; + return from_range(input_frequency, desired_frequency, range); } /// From any linear range. /// @note the range end is *inclusive*: [begin, end]. + /// @pre begin must be smaller or equal than end! static constexpr Result - from_range(T input_frequency, - T desired_frequency, - uint32_t begin, uint32_t end) + from_linear(T input_frequency, T desired_frequency, uint32_t begin, uint32_t end) { const double desired = double(input_frequency) / desired_frequency; - const uint32_t prescaler_floor = std::max(uint32_t(std::floor(desired)), begin); - const uint32_t prescaler_ceiling = std::min(uint32_t(std::ceil(desired)), end); + const uint32_t prescaler_floor = std::max(uint32_t(desired), begin); + const uint32_t prescaler_ceiling = std::min(uint32_t(desired) + 1, end); const T baud_lower = input_frequency / prescaler_ceiling; const T baud_upper = input_frequency / prescaler_floor; const double baud_middle = (baud_upper + baud_lower) / 2.0; @@ -149,12 +154,10 @@ GenericPrescaler /// From any 2-logarithmic range. /// @note the range end is *inclusive*: [begin, end]. - /// @param begin must be a power-of-two! - /// @param end must be a power-of-two! + /// @pre `begin` and `end` must be powers of two! + /// @pre `begin` must be smaller or equal than `end`! static constexpr Result - from_power(T input_frequency, - T desired_frequency, - uint32_t begin, uint32_t end) + from_power(T input_frequency, T desired_frequency, uint32_t begin, uint32_t end) { const double desired = double(input_frequency) / desired_frequency; uint32_t ii{0}; uint32_t power{begin}; diff --git a/src/modm/math/algorithm/prescaler_counter.hpp b/src/modm/math/algorithm/prescaler_counter.hpp new file mode 100644 index 0000000000..aa11407f56 --- /dev/null +++ b/src/modm/math/algorithm/prescaler_counter.hpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2024, 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 +#include +#include "enumerate.hpp" + +namespace modm +{ + +/** + * Computes the best fitting prescaler and counter value from a list. + * + * @note For ranges the end is *inclusive*: [begin, end]! + * @ingroup modm_math_algorithm + */ +template +class +GenericPrescalerCounter +{ +public: + struct Result + { + constexpr Result(T input_frequency, T desired_frequency, + uint32_t index, uint32_t prescaler, uint32_t counter) : + index{index}, prescaler{prescaler}, counter{counter}, + frequency{input_frequency / (prescaler * counter)}, + input_frequency{input_frequency}, + desired_frequency{desired_frequency}, + error{1.f - float(input_frequency) / float(prescaler * counter * desired_frequency)} + {} + /// Zero-indexed prescaler result + uint32_t index; + /// Prescaler value + uint32_t prescaler; + /// Counter value + uint32_t counter; + /// Generated frequency + T frequency; + /// Input frequency + T input_frequency; + /// Desired Frequency + T desired_frequency; + /// Relative Frequency Error + float error; + }; + +public: + /// From any forward range. + static constexpr Result + from_range(T input_frequency, T desired_frequency, uint32_t max_counter, + std::ranges::forward_range auto&& prescalers) + { + const double desired = double(input_frequency) / desired_frequency; + const auto [min, max] = std::ranges::minmax_element(prescalers); + // clamp to lowest frequency + if (desired < (*min * 1)) { + return {input_frequency, desired_frequency, + uint32_t(std::distance(std::ranges::begin(prescalers), min)), + *min, 1}; + } + // clamp to highest frequency + if (desired > (*max * max_counter)) { + return {input_frequency, desired_frequency, + uint32_t(std::distance(std::ranges::begin(prescalers), max)), + *max, max_counter}; + } + + uint32_t out_prescaler{0}, out_index{0}, out_counter{0}; + double out_error{std::numeric_limits::infinity()}; + auto fn_compare = [&](uint32_t idx, uint32_t pre, uint32_t cnt) + { + const double err = std::abs(desired - pre * cnt); + if (err < out_error) { + out_error = err; + out_prescaler = pre; + out_counter = cnt; + out_index = idx; + } + }; + for (const auto&& [idx, prescaler] : enumerate(prescalers)) + { + if (max_counter * prescaler < desired) continue; + const auto cnt = std::max(1, desired / prescaler); + fn_compare(idx, prescaler, cnt); + if (cnt > max_counter - 1) continue; + fn_compare(idx, prescaler, cnt + 1); + } + return {input_frequency, desired_frequency, out_index, out_prescaler, out_counter}; + } + + /// From a initializer list. + static constexpr Result + from_list(T input_frequency, T desired_frequency, uint32_t max_counter, + std::initializer_list prescalers) + { + return from_range(input_frequency, desired_frequency, max_counter, prescalers); + } + + /// From any linear range via modifier function. + /// @note the range end is *inclusive*: [begin, end]. + template< typename Function > + static constexpr Result + from_function(T input_frequency, T desired_frequency, uint32_t max_counter, + uint32_t begin, uint32_t end, Function prescaler_modifier) + { + struct linear_range_iterator + { + using iterator_category [[maybe_unused]] = std::forward_iterator_tag; + using value_type [[maybe_unused]] = uint32_t; + using difference_type [[maybe_unused]] = int32_t; + using pointer [[maybe_unused]] = value_type*; + using reference [[maybe_unused]] = value_type&; + + value_type state; + value_type value; + Function *modifier; + bool reversed; + + constexpr value_type operator*() const { return (*modifier)(value); } + constexpr linear_range_iterator& operator++() { state++; reversed ? value-- : value++; return *this; } + constexpr linear_range_iterator operator++(int) { auto old(*this); ++*this; return old; } + constexpr bool operator==(const linear_range_iterator &o) const { return state == o.state; } + constexpr difference_type operator-(const linear_range_iterator &o) const { return state - o.state; } + }; + struct linear_range + { + uint32_t m_begin; + uint32_t m_end; + Function *modifier; + bool reversed; + + constexpr linear_range_iterator begin() const + { return {m_begin, reversed ? m_end-1 : m_begin, modifier, reversed}; } + constexpr linear_range_iterator end() const + { return {m_end, reversed ? m_begin : m_end-1, modifier, reversed}; } + constexpr size_t size() const { return m_end - m_begin; } + }; + const bool reversed = begin > end; + if (reversed) std::swap(begin, end); + linear_range range{begin, end+1, &prescaler_modifier, reversed}; + return from_range(input_frequency, desired_frequency, max_counter, range); + } + + /// From any linear range. + /// @note the range end is *inclusive*: [begin, end]. + static constexpr Result + from_linear(T input_frequency, T desired_frequency, uint32_t max_counter, + uint32_t begin, uint32_t end) + { + return from_function(input_frequency, desired_frequency, max_counter, + begin, end, [](uint32_t ii) { return ii; }); + } + + /// From any 2-logarithmic range. + /// @note the range end is *inclusive*: [begin, end]. + /// @param begin must be a power-of-two! + /// @param end must be a power-of-two! + static constexpr Result + from_power(T input_frequency, T desired_frequency, uint32_t max_counter, + uint32_t begin, uint32_t end) + { + return from_function(input_frequency, desired_frequency, max_counter, + std::bit_width(begin - 1), std::bit_width(end - 1), + [](uint32_t ii) { return 1ul << ii; }); + } +}; + +using PrescalerCounter = GenericPrescalerCounter; + +} // namespace modm diff --git a/src/modm/platform/clock/systick/systick_timer.hpp.in b/src/modm/platform/clock/systick/systick_timer.hpp.in index be3882cc40..c4fd39d7d3 100644 --- a/src/modm/platform/clock/systick/systick_timer.hpp.in +++ b/src/modm/platform/clock/systick/systick_timer.hpp.in @@ -43,7 +43,7 @@ public: if constexpr (SystemClock::Frequency < {{ref_clk}}'000'000) %% endif { - constexpr auto result = Prescaler::from_range( + constexpr auto result = Prescaler::from_linear( SystemClock::Frequency, {{ systick_frequency }}, 1, (1ul << 24)-1); PeripheralDriver::assertBaudrateInTolerance< result.frequency, {{ systick_frequency }}, tolerance >(); @@ -56,7 +56,7 @@ public: %% if ref_clk > 1 else { - constexpr auto result = Prescaler::from_range( + constexpr auto result = Prescaler::from_linear( SystemClock::Frequency/{{ref_clk}}, {{ systick_frequency }}, 1, (1ul << 24)-1); PeripheralDriver::assertBaudrateInTolerance< result.frequency, {{ systick_frequency }}, tolerance >(); diff --git a/src/modm/platform/i2c/stm32/i2c_master.hpp.in b/src/modm/platform/i2c/stm32/i2c_master.hpp.in index 7364509da5..06459aa04e 100644 --- a/src/modm/platform/i2c/stm32/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32/i2c_master.hpp.in @@ -76,7 +76,7 @@ public: constexpr uint8_t scalar = (baudrate <= 100'000) ? 2 : ((baudrate <= 300'000) ? 3 : 25); constexpr uint16_t range_begin = (scalar == 2) ? 4 : 1; - constexpr auto result = Prescaler::from_range( + constexpr auto result = Prescaler::from_linear( SystemClock::I2c{{ id }} / scalar, baudrate, range_begin, 4095); assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); diff --git a/src/modm/platform/spi/rp/spi_master.hpp.in b/src/modm/platform/spi/rp/spi_master.hpp.in index e8676bc22b..054c843a5b 100644 --- a/src/modm/platform/spi/rp/spi_master.hpp.in +++ b/src/modm/platform/spi/rp/spi_master.hpp.in @@ -121,7 +121,7 @@ public: // 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 postdiv = Prescaler::from_linear(freq_in / prescale, baudrate, 1, 256).prescaler; constexpr uint32_t result_baudrate = freq_in / (prescale * postdiv); diff --git a/src/modm/platform/spi/sam/spi_master.hpp.in b/src/modm/platform/spi/sam/spi_master.hpp.in index ed5726dd46..a6f9ef804d 100644 --- a/src/modm/platform/spi/sam/spi_master.hpp.in +++ b/src/modm/platform/spi/sam/spi_master.hpp.in @@ -93,7 +93,7 @@ public: FLEXCOM{{ id }}->FLEXCOM_MR = FLEXCOM_MR_OPMODE_SPI; // Note: SPI peripheral supports operating from a programmable clock // output, but here we only use the main peripheral clock - constexpr auto result = modm::Prescaler::from_range(SystemClock::Frequency, baudrate, 2, 256); + constexpr auto result = modm::Prescaler::from_linear(SystemClock::Frequency, baudrate, 2, 256); assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); Regs()->SPI_CSR[0] = SPI_CSR_SCBR(result.prescaler); diff --git a/src/modm/platform/spi/sam_x7x/spi_master.hpp.in b/src/modm/platform/spi/sam_x7x/spi_master.hpp.in index 9f40cc092f..3cd2f1326a 100644 --- a/src/modm/platform/spi/sam_x7x/spi_master.hpp.in +++ b/src/modm/platform/spi/sam_x7x/spi_master.hpp.in @@ -74,7 +74,7 @@ public: static void initialize() { - constexpr auto result = modm::Prescaler::from_range(SystemClock::Spi{{ id }}, baudrate, 1, 255); + constexpr auto result = modm::Prescaler::from_linear(SystemClock::Spi{{ id }}, baudrate, 1, 255); assertBaudrateInTolerance(); SpiHal{{ id }}::initialize(result.prescaler); diff --git a/src/modm/platform/uart/at90_tiny_mega/uart.hpp.in b/src/modm/platform/uart/at90_tiny_mega/uart.hpp.in index fba51e803a..512e1d8697 100644 --- a/src/modm/platform/uart/at90_tiny_mega/uart.hpp.in +++ b/src/modm/platform/uart/at90_tiny_mega/uart.hpp.in @@ -67,7 +67,7 @@ public: { // use double speed when necessary constexpr uint32_t scalar = (baudrate * 16l > SystemClock::Uart) ? 8 : 16; - constexpr auto result = Prescaler::from_range( + constexpr auto result = Prescaler::from_linear( SystemClock::Uart / scalar, baudrate, 1, 4095); assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); constexpr uint16_t ubrr = result.index | ((scalar == 8) ? 0x8000 : 0); diff --git a/src/modm/platform/uart/rp/uart.hpp.in b/src/modm/platform/uart/rp/uart.hpp.in index f45085b7ef..ae22c1aa5f 100644 --- a/src/modm/platform/uart/rp/uart.hpp.in +++ b/src/modm/platform/uart/rp/uart.hpp.in @@ -112,7 +112,7 @@ public: // 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); + constexpr auto result = Prescaler::from_linear(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; diff --git a/src/modm/platform/uart/sam-sercom/uart_hal_impl.hpp.in b/src/modm/platform/uart/sam-sercom/uart_hal_impl.hpp.in index 8947a40bee..629f5aee38 100644 --- a/src/modm/platform/uart/sam-sercom/uart_hal_impl.hpp.in +++ b/src/modm/platform/uart/sam-sercom/uart_hal_impl.hpp.in @@ -75,7 +75,7 @@ void constexpr uint32_t scalar = (baudrate * 16l > SystemClock::{{ sercom | capitalize }}) ? 8 : 16; {{ peripheral }}->USART.CTRLA.bit.SAMPR = (scalar == 16) ? 0x1 : 0x3; // Prescaler 13 bit integer, 3 bit fractional - constexpr auto result = Prescaler::from_range( + constexpr auto result = Prescaler::from_linear( SystemClock::{{ sercom | capitalize }}/(scalar/8), baudrate, 1, (1ul << 16) - 1ul); assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); {{ peripheral }}->USART.BAUD.FRAC.BAUD = result.prescaler >> 3; diff --git a/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in b/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in index 3232d73b2b..821b307356 100644 --- a/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in +++ b/src/modm/platform/uart/stm32/uart_hal_impl.hpp.in @@ -70,20 +70,20 @@ void %% if over8 constexpr uint32_t scalar = (baudrate * 16 > SystemClock::{{ name }}) ? 8 : 16; constexpr uint32_t max = ((scalar == 16) ? (1ul << 16) : (1ul << 15)) - 1ul; - constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, scalar, max); + constexpr auto result = Prescaler::from_linear(SystemClock::{{ name }}, baudrate, scalar, max); %% elif uart_type == "Lpuart" static_assert(SystemClock::{{ name }} >= baudrate * 3, "fck must be in the range [3 x baud rate, 4096 x baud rate]."); static_assert(SystemClock::{{ name }} <= uint64_t(baudrate) * 4096, "fck must be in the range [3 x baud rate, 4096 x baud rate]."); constexpr uint32_t max = (1ul << 20) - 1ul; - constexpr auto result = GenericPrescaler::from_range( + constexpr auto result = GenericPrescaler::from_linear( uint64_t(SystemClock::{{ name }}) * 256, baudrate, 0x300, max); %% else constexpr uint32_t max = (1ul << 16) - 1ul; - constexpr auto result = Prescaler::from_range(SystemClock::{{ name }}, baudrate, 16, max); + constexpr auto result = Prescaler::from_linear(SystemClock::{{ name }}, baudrate, 16, max); %% endif modm::PeripheralDriver::assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); diff --git a/test/modm/math/algorithm/prescaler_counter_test.cpp b/test/modm/math/algorithm/prescaler_counter_test.cpp new file mode 100644 index 0000000000..8410c08faa --- /dev/null +++ b/test/modm/math/algorithm/prescaler_counter_test.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2024, 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 +#include + +#include "prescaler_counter_test.hpp" +using namespace modm::literals; + +using Prescaler = modm::GenericPrescalerCounter; + +void +PrescalerCounterTest::testList() +{ + // one prescaler + { + const auto result = Prescaler::from_list( + 32_kHz, 1_Hz, 1ul << 12, {8}); + TEST_ASSERT_EQUALS(result.index, 0ul); + TEST_ASSERT_EQUALS(result.prescaler, 8ul); + TEST_ASSERT_EQUALS(result.counter, 4000ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.0); + TEST_ASSERT_EQUALS_FLOAT(result.error, 0.0); + } + // two prescaler 1 + { + const auto result = Prescaler::from_list( + 32_kHz, 1_Hz, 1ul << 12, {4, 8}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 8ul); + TEST_ASSERT_EQUALS(result.counter, 4000ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.0); + TEST_ASSERT_EQUALS_FLOAT(result.error, 0.0); + } + // two prescaler 2 + { + const auto result = Prescaler::from_list( + 32_kHz, 2_Hz, 1ul << 12, {9, 11}); + TEST_ASSERT_EQUALS(result.index, 0ul); + TEST_ASSERT_EQUALS(result.prescaler, 9ul); + TEST_ASSERT_EQUALS(result.counter, 1778ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.99975f); + TEST_ASSERT_EQUALS_FLOAT(result.error, 0.000125); + } + // exact matches + { + const auto result = Prescaler::from_list( + 40_kHz, 4_Hz, 1ul << 16, {7,8,9,10,11,12}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 8ul); + TEST_ASSERT_EQUALS(result.counter, 1250ul); + TEST_ASSERT_EQUALS(result.frequency, 4); + TEST_ASSERT_EQUALS(result.error, 0); + } + { + const auto result = Prescaler::from_list( + 32_kHz, 0.25, 1ul << 10, {128,64,32}); + TEST_ASSERT_EQUALS(result.index, 0ul); + TEST_ASSERT_EQUALS(result.prescaler, 128ul); + TEST_ASSERT_EQUALS(result.counter, 1000ul); + TEST_ASSERT_EQUALS(result.frequency, 0.25); + TEST_ASSERT_EQUALS(result.error, 0); + } + // inexact matches within prescaler range + { + const auto result = Prescaler::from_list( + 32_kHz, 1.902515, 1ul << 12, {3,5,7,11}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 5ul); + TEST_ASSERT_EQUALS(result.counter, 3364ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.9025f); + TEST_ASSERT_EQUALS_FLOAT(result.error, 0.00001); + } + { + const auto result = Prescaler::from_list( + 2_kHz, 1.093948, 1ul << 8, {11,13,17}); + TEST_ASSERT_EQUALS(result.index, 0ul); + TEST_ASSERT_EQUALS(result.prescaler, 11ul); + TEST_ASSERT_EQUALS(result.counter, 166ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.095290f); + TEST_ASSERT_EQUALS_FLOAT(result.error, -0.00123); + } + { + const auto result = Prescaler::from_list( + 2_kHz, 1.25, 1ul << 6, {23,29,31}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 29ul); + TEST_ASSERT_EQUALS(result.counter, 55ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1.253918); + TEST_ASSERT_EQUALS_FLOAT(result.error, -0.003135); + } + // inexact matches on error border within prescaler range + { + const auto result = Prescaler::from_list( + 1_kHz, 0.8138020833, 1ul << 6, {1024}); // ±20% + TEST_ASSERT_EQUALS(result.index, 0ul); + TEST_ASSERT_EQUALS(result.prescaler, 1024ul); + TEST_ASSERT_EQUALS(result.counter, 1ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 0.9765625); + TEST_ASSERT_EQUALS_FLOAT(result.error, -0.2); + } + // clamping below prescaler range + { + const auto result = Prescaler::from_list( + 1_kHz, 10_kHz, 1ul << 10, {9,1,10,2}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 1ul); + TEST_ASSERT_EQUALS(result.counter, 1ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 1000); + TEST_ASSERT_EQUALS_FLOAT(result.error, 0.9); + } + // clamping above prescaler range + { + const auto result = Prescaler::from_list( + 10_kHz, 1_Hz, 1ul << 10, {1,4,3,2}); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 4ul); + TEST_ASSERT_EQUALS(result.counter, 1024ul); + TEST_ASSERT_EQUALS_FLOAT(result.frequency, 2.44140625); + TEST_ASSERT_EQUALS_FLOAT(result.error, -1.44141); + } + // const with static_assert + { + constexpr auto result = Prescaler::from_list( + 10_kHz, 1_Hz, 1ul << 10, {51,7,11}); + static_assert(result.index == 2ul); + static_assert(result.prescaler == 11ul); + static_assert(result.counter == 909); + static_assert(result.frequency - 1.0001 < 0.01); + static_assert(result.error - -0.00001 < 0.01); + } +} + +void +PrescalerCounterTest::testFunction() +{ + // implemented via iterator + { + const auto result = Prescaler::from_function( + 32_kHz, 2_Hz, 1ul << 12, 9, 11, [](uint32_t ii) { return 2*ii; }); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 20ul); + TEST_ASSERT_EQUALS(result.counter, 800ul); + TEST_ASSERT_EQUALS(result.frequency, 2); + TEST_ASSERT_EQUALS(result.error, 0); + } + { + const auto result = Prescaler::from_function( + 32_kHz, 2_Hz, 1ul << 12, 11, 9, [](uint32_t ii) { return 2*ii; }); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 20ul); + TEST_ASSERT_EQUALS(result.counter, 800ul); + TEST_ASSERT_EQUALS(result.frequency, 2); + TEST_ASSERT_EQUALS(result.error, 0); + } +} + +void +PrescalerCounterTest::testLinear() +{ + // implemented via iterator + { + const auto result = Prescaler::from_linear( + 40_kHz, 4_Hz, 1ul << 10, 8, 12); + TEST_ASSERT_EQUALS(result.index, 2ul); + TEST_ASSERT_EQUALS(result.prescaler, 10ul); + TEST_ASSERT_EQUALS(result.counter, 1000ul); + TEST_ASSERT_EQUALS(result.frequency, 4); + TEST_ASSERT_EQUALS(result.error, 0); + } + { + const auto result = Prescaler::from_linear( + 40_kHz, 4_Hz, 1ul << 10, 12, 8); + TEST_ASSERT_EQUALS(result.index, 2ul); + TEST_ASSERT_EQUALS(result.prescaler, 10ul); + TEST_ASSERT_EQUALS(result.counter, 1000ul); + TEST_ASSERT_EQUALS(result.frequency, 4); + TEST_ASSERT_EQUALS(result.error, 0); + } +} + +void +PrescalerCounterTest::testPower() +{ + // implemented via iterator + { + const auto result = Prescaler::from_power( + 40_kHz, 4_Hz, 1ul << 10, 8, 32); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 16ul); + TEST_ASSERT_EQUALS(result.counter, 625ul); + TEST_ASSERT_EQUALS(result.frequency, 4); + TEST_ASSERT_EQUALS(result.error, 0); + } + { + const auto result = Prescaler::from_power( + 40_kHz, 4_Hz, 1ul << 10, 32, 8); + TEST_ASSERT_EQUALS(result.index, 1ul); + TEST_ASSERT_EQUALS(result.prescaler, 16ul); + TEST_ASSERT_EQUALS(result.counter, 625ul); + TEST_ASSERT_EQUALS(result.frequency, 4); + TEST_ASSERT_EQUALS(result.error, 0); + } +} diff --git a/test/modm/math/algorithm/prescaler_counter_test.hpp b/test/modm/math/algorithm/prescaler_counter_test.hpp new file mode 100644 index 0000000000..65669a9c4a --- /dev/null +++ b/test/modm/math/algorithm/prescaler_counter_test.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, 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 + +/// @ingroup modm_test_test_math +class PrescalerCounterTest : public unittest::TestSuite +{ +public: + void + testList(); + + void + testFunction(); + + void + testLinear(); + + void + testPower(); +}; diff --git a/test/modm/math/algorithm/prescaler_test.cpp b/test/modm/math/algorithm/prescaler_test.cpp index 134cba118c..90b0ae2aff 100644 --- a/test/modm/math/algorithm/prescaler_test.cpp +++ b/test/modm/math/algorithm/prescaler_test.cpp @@ -169,11 +169,11 @@ PrescalerTest::testFunction() } void -PrescalerTest::testRange() +PrescalerTest::testLinear() { // one prescaler { - const auto result = modm::Prescaler::from_range( + const auto result = modm::Prescaler::from_linear( 1_MHz, 0.25_MHz, 1, 1); TEST_ASSERT_EQUALS(result.index, 0ul); TEST_ASSERT_EQUALS(result.prescaler, 1ul); @@ -181,7 +181,7 @@ PrescalerTest::testRange() } // two prescalers { - const auto result = modm::Prescaler::from_range( + const auto result = modm::Prescaler::from_linear( 1_MHz, 0.25_MHz, 1, 2); TEST_ASSERT_EQUALS(result.index, 1ul); TEST_ASSERT_EQUALS(result.prescaler, 2ul); @@ -189,7 +189,7 @@ PrescalerTest::testRange() } // clamp lower range { - const auto result = modm::Prescaler::from_range( + const auto result = modm::Prescaler::from_linear( 1_MHz, 2_MHz, 1, 10); TEST_ASSERT_EQUALS(result.index, 0ul); TEST_ASSERT_EQUALS(result.prescaler, 1ul); @@ -197,7 +197,7 @@ PrescalerTest::testRange() } // clamp upper range { - const auto result = modm::Prescaler::from_range( + const auto result = modm::Prescaler::from_linear( 1_MHz, 0.05_MHz, 1, 10); TEST_ASSERT_EQUALS(result.index, 9ul); TEST_ASSERT_EQUALS(result.prescaler, 10ul); @@ -205,7 +205,7 @@ PrescalerTest::testRange() } // decide on error border { - const auto result = modm::Prescaler::from_range( + const auto result = modm::Prescaler::from_linear( 1_MHz, 0.75_MHz, 1, 10); TEST_ASSERT_EQUALS(result.index, 1ul); TEST_ASSERT_EQUALS(result.prescaler, 2ul); diff --git a/test/modm/math/algorithm/prescaler_test.hpp b/test/modm/math/algorithm/prescaler_test.hpp index bed1eafc45..dca1d50692 100644 --- a/test/modm/math/algorithm/prescaler_test.hpp +++ b/test/modm/math/algorithm/prescaler_test.hpp @@ -22,7 +22,7 @@ class PrescalerTest : public unittest::TestSuite testFunction(); void - testRange(); + testLinear(); void testPower(); From f647254c53939f9b3f84a3cf8a267cc94b94f36a Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 14 Jan 2024 20:12:18 +0100 Subject: [PATCH 128/159] [stm32] Simplify IWDG driver by inlining functions --- src/modm/platform/iwdg/stm32/iwdg.cpp | 47 -------------------- src/modm/platform/iwdg/stm32/iwdg.hpp | 61 +++++++++++++++++++------- src/modm/platform/iwdg/stm32/module.lb | 10 +++-- 3 files changed, 51 insertions(+), 67 deletions(-) delete mode 100644 src/modm/platform/iwdg/stm32/iwdg.cpp diff --git a/src/modm/platform/iwdg/stm32/iwdg.cpp b/src/modm/platform/iwdg/stm32/iwdg.cpp deleted file mode 100644 index 1147e7eec1..0000000000 --- a/src/modm/platform/iwdg/stm32/iwdg.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2023, Zühlke Engineering (Austria) GmbH - * - * 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 "iwdg.hpp" - -void -Iwdg::initialize(Iwdg::Prescaler prescaler, uint32_t reload) -{ - writeKey(writeCommand); - IWDG->PR = (IWDG->PR & ~static_cast(Iwdg::Prescaler::All)) | - static_cast(prescaler); - IWDG->RLR = (IWDG->RLR & ~IWDG_RLR_RL) | (reload << IWDG_RLR_RL_Pos); - writeKey(0); // disable access to PR and RLR registers -} - -void -Iwdg::enable() -{ - writeKey(enableCommand); -} - -void -Iwdg::trigger() -{ - writeKey(reloadCommand); -} - -void -Iwdg::writeKey(uint32_t key) -{ - IWDG->KR = (IWDG->KR & ~IWDG_KR_KEY_Msk) | ((key & IWDG_KR_KEY_Msk) << IWDG_KR_KEY_Pos); -} - -Iwdg::Status -Iwdg::getStatus() -{ - const auto status = IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU); - return static_cast(status); -}; \ No newline at end of file diff --git a/src/modm/platform/iwdg/stm32/iwdg.hpp b/src/modm/platform/iwdg/stm32/iwdg.hpp index d81b2a1e48..cb1ef1527c 100644 --- a/src/modm/platform/iwdg/stm32/iwdg.hpp +++ b/src/modm/platform/iwdg/stm32/iwdg.hpp @@ -12,11 +12,16 @@ #pragma once #include "../device.hpp" + +namespace modm::platform +{ + +/// @ingroup modm_platform_iwdg class Iwdg { public: enum class - Prescaler : uint32_t + Prescaler : uint8_t { Div4 = 0, Div8 = IWDG_PR_PR_0, @@ -29,7 +34,7 @@ class Iwdg }; enum class - Status : uint32_t + Status : uint8_t { None = 0, Prescaler = IWDG_SR_PVU, @@ -37,20 +42,44 @@ class Iwdg All = IWDG_SR_PVU | IWDG_SR_RVU, }; - static void - initialize(Prescaler prescaler, uint32_t reload); - static void - enable(); - static void - trigger(); - static Status - getStatus(); +public: + static inline void + initialize(Prescaler prescaler, uint32_t reload) + { + writeKey(writeCommand); + IWDG->PR = uint32_t(prescaler); + IWDG->RLR = reload; + writeKey(0); // disable access to PR and RLR registers + } + + static inline void + enable() + { + writeKey(enableCommand); + } + + static inline void + trigger() + { + writeKey(reloadCommand); + } + + static inline Status + getStatus() + { + return Status(IWDG->SR); + } private: - static constexpr uint32_t reloadCommand = 0xAAAA; - static constexpr uint32_t writeCommand = 0x5555; - static constexpr uint32_t enableCommand = 0xCCCC; + static constexpr uint16_t reloadCommand = 0xAAAA; + static constexpr uint16_t writeCommand = 0x5555; + static constexpr uint16_t enableCommand = 0xCCCC; + + static inline void + writeKey(uint16_t key) + { + IWDG->KR = key; + } +}; - static void - writeKey(uint32_t key); -}; \ No newline at end of file +} diff --git a/src/modm/platform/iwdg/stm32/module.lb b/src/modm/platform/iwdg/stm32/module.lb index f1cc92ad69..49f172d8ff 100644 --- a/src/modm/platform/iwdg/stm32/module.lb +++ b/src/modm/platform/iwdg/stm32/module.lb @@ -14,15 +14,17 @@ def init(module): module.name = ":platform:iwdg" module.description = "Independent watchdog" + def prepare(module, options): device = options[":target"] - target = device.identifier - if target["family"] in ["h7"]: - # STM32H7 is not yet supported with any IWDG implementation im modm + # STM32H7 is not yet supported with any IWDG implementation + if device.identifier.family in ["h7"]: return False + + module.depends(":cmsis:device") return device.has_driver("iwdg:stm32") + def build(env): env.outbasepath = "modm/src/modm/platform/iwdg" env.copy("iwdg.hpp") - env.copy("iwdg.cpp") From 6d7262280b001e4bbb9c52a7743ae80e428b34c5 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 28 Jan 2024 19:48:22 +0100 Subject: [PATCH 129/159] [iwdg] Add initialize() with prescaler calculator --- .../{independend_watchdog => iwdg}/main.cpp | 3 +- .../project.xml | 0 .../architecture/interface/peripheral.hpp | 10 +++++- src/modm/platform/iwdg/stm32/iwdg.hpp | 36 +++++++++++++------ src/modm/platform/iwdg/stm32/module.lb | 2 +- 5 files changed, 38 insertions(+), 13 deletions(-) rename examples/nucleo_f072rb/{independend_watchdog => iwdg}/main.cpp (94%) rename examples/nucleo_f072rb/{independend_watchdog => iwdg}/project.xml (100%) diff --git a/examples/nucleo_f072rb/independend_watchdog/main.cpp b/examples/nucleo_f072rb/iwdg/main.cpp similarity index 94% rename from examples/nucleo_f072rb/independend_watchdog/main.cpp rename to examples/nucleo_f072rb/iwdg/main.cpp index a767c4d821..bd3d79f3e2 100644 --- a/examples/nucleo_f072rb/independend_watchdog/main.cpp +++ b/examples/nucleo_f072rb/iwdg/main.cpp @@ -13,6 +13,7 @@ #include using namespace Board; +using namespace std::chrono_literals; /** * If the button is pressed for more than 4 seconds, the MCU will be reset by the Watchdog. @@ -25,7 +26,7 @@ main() Board::initialize(); LedD13::setOutput(); // set the watchdog timeout to 4 seconds - Iwdg::initialize(Iwdg::Prescaler::Div32, 0x0FFFu); + Iwdg::initialize(); // Use the logging streams to print some messages. // Change MODM_LOG_LEVEL above to enable or disable these messages diff --git a/examples/nucleo_f072rb/independend_watchdog/project.xml b/examples/nucleo_f072rb/iwdg/project.xml similarity index 100% rename from examples/nucleo_f072rb/independend_watchdog/project.xml rename to examples/nucleo_f072rb/iwdg/project.xml diff --git a/src/modm/architecture/interface/peripheral.hpp b/src/modm/architecture/interface/peripheral.hpp index 4cdf4fb1f6..ac122b7097 100644 --- a/src/modm/architecture/interface/peripheral.hpp +++ b/src/modm/architecture/interface/peripheral.hpp @@ -84,13 +84,21 @@ class PeripheralDriver * This method checks if the user requested baudrate is within error * tolerance of the system achievable baudrate. */ - template< baudrate_t available, baudrate_t requested, percent_t tolerance > + template< uint64_t available, uint64_t requested, percent_t tolerance > static void assertBaudrateInTolerance() { static_assert(modm::isValueInTolerance(requested, available, tolerance), "The closest available baudrate exceeds the tolerance of the requested baudrate!"); } + + template< double available, double requested, percent_t tolerance > + static void + assertDurationInTolerance() + { + static_assert(modm::isValueInTolerance(requested, available, tolerance), + "The closest available duration exceeds the tolerance of the requested duration!"); + } }; } // namespace modm diff --git a/src/modm/platform/iwdg/stm32/iwdg.hpp b/src/modm/platform/iwdg/stm32/iwdg.hpp index cb1ef1527c..26321b6c5a 100644 --- a/src/modm/platform/iwdg/stm32/iwdg.hpp +++ b/src/modm/platform/iwdg/stm32/iwdg.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, Zühlke Engineering (Austria) GmbH + * Copyright (c) 2024, Niklas Hauser * * This file is part of the modm project. * @@ -10,6 +11,8 @@ // ---------------------------------------------------------------------------- #pragma once +#include +#include #include "../device.hpp" @@ -17,7 +20,7 @@ namespace modm::platform { /// @ingroup modm_platform_iwdg -class Iwdg +class Iwdg : public ::modm::PeripheralDriver { public: enum class @@ -43,15 +46,19 @@ class Iwdg }; public: - static inline void - initialize(Prescaler prescaler, uint32_t reload) + template< class SystemClock, milliseconds_t timeout, percent_t tolerance=pct(1) > + static void + initialize() { - writeKey(writeCommand); - IWDG->PR = uint32_t(prescaler); - IWDG->RLR = reload; - writeKey(0); // disable access to PR and RLR registers + constexpr double frequency = 1000.0 / timeout.count(); + constexpr auto result = modm::GenericPrescalerCounter::from_power( + SystemClock::Iwdg, frequency, 1ul << 12, 256, 4); + assertDurationInTolerance< 1.0 / result.frequency, 1.0 / frequency, tolerance >(); + + configure(Prescaler(result.index), result.counter - 1); } + static inline void enable() { @@ -71,15 +78,24 @@ class Iwdg } private: - static constexpr uint16_t reloadCommand = 0xAAAA; - static constexpr uint16_t writeCommand = 0x5555; - static constexpr uint16_t enableCommand = 0xCCCC; + static inline void + configure(Prescaler prescaler, uint16_t reload) + { + writeKey(writeCommand); + IWDG->PR = uint32_t(prescaler); + IWDG->RLR = reload; + writeKey(0); // disable access to PR and RLR registers + } static inline void writeKey(uint16_t key) { IWDG->KR = key; } + + static constexpr uint16_t reloadCommand = 0xAAAA; + static constexpr uint16_t writeCommand = 0x5555; + static constexpr uint16_t enableCommand = 0xCCCC; }; } diff --git a/src/modm/platform/iwdg/stm32/module.lb b/src/modm/platform/iwdg/stm32/module.lb index 49f172d8ff..98536a5b64 100644 --- a/src/modm/platform/iwdg/stm32/module.lb +++ b/src/modm/platform/iwdg/stm32/module.lb @@ -21,7 +21,7 @@ def prepare(module, options): if device.identifier.family in ["h7"]: return False - module.depends(":cmsis:device") + module.depends(":cmsis:device", ":math:algorithm") return device.has_driver("iwdg:stm32") From 23036e3686b60ba28dc956da5625a6f349737cfa Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 3 Feb 2024 13:28:55 +0100 Subject: [PATCH 130/159] [ci] Remove unused TravisCI configuration --- .travis.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b3d1edb171..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -os: linux -dist: focal # Ubuntu 20.04LTS -arch: arm64 -#arch: arm64-graviton2 - -language: minimal - -before_install: - - sudo apt-get update - - sudo apt-get install -y python3 python3-pip scons cmake doxygen gcc-10 g++-10 build-essential libboost-all-dev libwiringpi-dev - - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 90 --slave /usr/bin/g++ g++ /usr/bin/g++-10 --slave /usr/bin/gcov gcov /usr/bin/gcov-10 - #- sudo apt-get install -y gcc-arm-none-eabi - - wget -qO- https://developer.arm.com/-/media/Files/downloads/gnu-rm/10-2020q4/gcc-arm-none-eabi-10-2020-q4-major-aarch64-linux.tar.bz2 | tar xvj -C /opt - - export PATH="/opt/gcc-arm-none-eabi-10-2020-q4-major/bin:$PATH" - - pip3 install modm - - export PATH="~/.local/bin:$PATH" - - uname -a - -script: - - (cd test && make run-hosted-linux) - - (cd examples && ../tools/scripts/examples_compile.py linux) - - (cd examples && ../tools/scripts/examples_compile.py stm32f1_discovery nucleo_f103rb olimexino_stm32 blue_pill_f103 black_pill_f103) - - (cd examples && ../tools/scripts/examples_compile.py rpi) From df012e41a0a104c0d509bd2b866736c8e3bde218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Waldh=C3=A4usl?= Date: Fri, 2 Feb 2024 03:04:36 -0800 Subject: [PATCH 131/159] [stm32] fix include path on dac implementation --- src/modm/platform/dac/stm32/dac_dma.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/platform/dac/stm32/dac_dma.hpp.in b/src/modm/platform/dac/stm32/dac_dma.hpp.in index a2af4e65eb..790ab1a247 100644 --- a/src/modm/platform/dac/stm32/dac_dma.hpp.in +++ b/src/modm/platform/dac/stm32/dac_dma.hpp.in @@ -17,7 +17,7 @@ #include #include #include -#include +#include namespace modm::platform { From efb8e947d3fdb8f62d6fde081990411ac24c47be Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Fri, 23 Feb 2024 20:15:45 +0100 Subject: [PATCH 132/159] [ci] Update Xcode version to 15.2 to fix linker bug --- .github/workflows/macos.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f9a54e21c4..e5a9051ea0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -7,6 +7,9 @@ jobs: runs-on: macos-13 steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.2' - name: Setup environment - Brew tap run: | From 2ac6250f799e26a0ac8870ae48ec3ced7fd6bd87 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Fri, 23 Feb 2024 20:40:45 +0100 Subject: [PATCH 133/159] [ci] Update deprecated actions --- .github/workflows/compile-all.yml | 114 +++++++++++++++--------------- .github/workflows/deploy-docs.yml | 10 +-- .github/workflows/linux.yml | 36 +++++----- .github/workflows/macos.yml | 4 +- .github/workflows/windows.yml | 4 +- 5 files changed, 84 insertions(+), 84 deletions(-) diff --git a/.github/workflows/compile-all.yml b/.github/workflows/compile-all.yml index 3f2dc9c2e0..e4e78ec4b6 100644 --- a/.github/workflows/compile-all.yml +++ b/.github/workflows/compile-all.yml @@ -12,7 +12,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-avr:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -26,7 +26,7 @@ jobs: (cd test/all && python3 run_all.py at --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: avr-compile-all path: test/all/log @@ -38,7 +38,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -52,9 +52,9 @@ jobs: (cd test/all && python3 run_all.py samd0 samd1 samd2 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: sam-compile-all + name: samdx-compile-all path: test/all/log sam-x5x-compile-all: @@ -64,7 +64,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -78,9 +78,9 @@ jobs: (cd test/all && python3 run_all.py samd5 same5 samg5 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: sam-compile-all + name: samx5-compile-all path: test/all/log sam-x7x-compile-all: @@ -90,7 +90,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -104,9 +104,9 @@ jobs: (cd test/all && python3 run_all.py same7 sams7 samv7 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: sam-compile-all + name: samx7-compile-all path: test/all/log stm32f0-compile-all: @@ -116,7 +116,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -130,7 +130,7 @@ jobs: (cd test/all && python3 run_all.py stm32f0 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f0-compile-all path: test/all/log @@ -142,7 +142,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -156,7 +156,7 @@ jobs: (cd test/all && python3 run_all.py stm32f1 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f1-compile-all path: test/all/log @@ -168,7 +168,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -182,7 +182,7 @@ jobs: (cd test/all && python3 run_all.py stm32f2 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f2-compile-all path: test/all/log @@ -194,7 +194,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -208,7 +208,7 @@ jobs: (cd test/all && python3 run_all.py stm32f3 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f3-compile-all path: test/all/log @@ -220,7 +220,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -234,7 +234,7 @@ jobs: (cd test/all && python3 run_all.py stm32f4 --quick-remaining --split 3 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f4-compile-all-1 path: test/all/log @@ -246,7 +246,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -260,7 +260,7 @@ jobs: (cd test/all && python3 run_all.py stm32f4 --quick-remaining --split 3 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f4-compile-all-2 path: test/all/log @@ -272,7 +272,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -286,7 +286,7 @@ jobs: (cd test/all && python3 run_all.py stm32f4 --quick-remaining --split 3 --part 2) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f4-compile-all-3 path: test/all/log @@ -298,7 +298,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -312,7 +312,7 @@ jobs: (cd test/all && python3 run_all.py stm32f7 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f7-compile-all-1 path: test/all/log @@ -324,7 +324,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -338,7 +338,7 @@ jobs: (cd test/all && python3 run_all.py stm32f7 --quick-remaining --split 2 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32f7-compile-all-2 path: test/all/log @@ -350,7 +350,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -364,7 +364,7 @@ jobs: (cd test/all && python3 run_all.py stm32l0 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l0-compile-all-1 path: test/all/log @@ -376,7 +376,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -390,7 +390,7 @@ jobs: (cd test/all && python3 run_all.py stm32l0 --quick-remaining --split 2 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l0-compile-all-2 path: test/all/log @@ -403,7 +403,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -417,7 +417,7 @@ jobs: (cd test/all && python3 run_all.py stm32l1 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l1-compile-all path: test/all/log @@ -429,7 +429,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -443,7 +443,7 @@ jobs: (cd test/all && python3 run_all.py stm32l4 --quick-remaining --split 3 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l4-compile-all-1 path: test/all/log @@ -455,7 +455,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -469,7 +469,7 @@ jobs: (cd test/all && python3 run_all.py stm32l4 --quick-remaining --split 3 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l4-compile-all-2 path: test/all/log @@ -481,7 +481,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -495,7 +495,7 @@ jobs: (cd test/all && python3 run_all.py stm32l4 --quick-remaining --split 3 --part 2) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l4-compile-all-3 path: test/all/log @@ -507,7 +507,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -521,7 +521,7 @@ jobs: (cd test/all && python3 run_all.py stm32l5 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32l5-compile-all path: test/all/log @@ -533,7 +533,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -547,7 +547,7 @@ jobs: (cd test/all && python3 run_all.py stm32u5 --quick-remaining) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32u5-compile-all path: test/all/log @@ -559,7 +559,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -573,7 +573,7 @@ jobs: (cd test/all && python3 run_all.py stm32g0 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32g0-compile-all-1 path: test/all/log @@ -585,7 +585,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -599,7 +599,7 @@ jobs: (cd test/all && python3 run_all.py stm32g0 --quick-remaining --split 2 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32g0-compile-all-2 path: test/all/log @@ -611,7 +611,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -625,7 +625,7 @@ jobs: (cd test/all && python3 run_all.py stm32g4 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32g4-compile-all-1 path: test/all/log @@ -637,7 +637,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -651,7 +651,7 @@ jobs: (cd test/all && python3 run_all.py stm32g4 --quick-remaining --split 2 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32g4-compile-all-2 path: test/all/log @@ -663,7 +663,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -677,7 +677,7 @@ jobs: (cd test/all && python3 run_all.py stm32h7 --quick-remaining --split 2 --part 0) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32h7-compile-all-1 path: test/all/log @@ -689,7 +689,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -703,7 +703,7 @@ jobs: (cd test/all && python3 run_all.py stm32h7 --quick-remaining --split 2 --part 1) - name: Upload log artifacts if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: stm32h7-compile-all-2 path: test/all/log diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 965863efe9..d6c1ed0e45 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -13,7 +13,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-base:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -27,7 +27,7 @@ jobs: mkdocs --version pip3 show mkdocs-material - name: Clone modm-ext/modm.io repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: modm-ext/modm.io ssh-key: ${{secrets.MODM_EXT_MODM_IO_DEPLOY_KEY}} @@ -41,8 +41,8 @@ jobs: - name: Push New Docs to Github run: | cd docs/modm.io - git config user.email "rca@circleci.com" - git config user.name "CircleCI Deployment Bot" + git config user.email "bot@modm.io" + git config user.name "modm bot" git add -A git diff-index --quiet HEAD || git commit -m "Update" git push origin master @@ -54,7 +54,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-base:2022-09-27 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c6da2de592..c379806d3a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -104,7 +104,7 @@ jobs: - name: Examples SAME70 Devices if: always() run: | - (cd examples && ../tools/scripts/examples_compile.py same70_xplained) + (cd examples && ../tools/scripts/examples_compile.py same70_xplained) - name: Examples SAMV Devices if: always() run: | @@ -125,7 +125,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -189,7 +189,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -209,7 +209,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -232,7 +232,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-avr:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -267,7 +267,7 @@ jobs: run: | (cd test/all && python3 run_all.py at --quick) - name: Upload log artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hal-compile-quick-avr path: test/all/log @@ -278,7 +278,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -292,7 +292,7 @@ jobs: run: | (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 0) - name: Upload log artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hal-compile-quick-1 path: test/all/log @@ -303,7 +303,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -317,7 +317,7 @@ jobs: run: | (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 1) - name: Upload log artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hal-compile-quick-2 path: test/all/log @@ -328,7 +328,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -342,7 +342,7 @@ jobs: run: | (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 2) - name: Upload log artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hal-compile-quick-3 path: test/all/log @@ -353,7 +353,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-cortex-m:2023-03-12 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -367,7 +367,7 @@ jobs: run: | (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 3) - name: Upload log artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hal-compile-quick-4 path: test/all/log @@ -380,7 +380,7 @@ jobs: image: ghcr.io/modm-ext/modm-build-base:2022-09-27 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - name: Fix Git permission/ownership problem @@ -401,7 +401,7 @@ jobs: export COLUMNS=120 python3 tools/scripts/docs_modm_io_generator.py -t -c -j4 -d - name: Upload Doxypress Documentation - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build-docs-test path: modm-api-docs.tar.gz @@ -416,7 +416,7 @@ jobs: ls -l docs/src/reference/module (cd docs && mkdocs build) - name: Upload Homepage Documentation - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build-homepage-test path: docs/modm.io diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e5a9051ea0..81caab334b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: brew link --force avr-gcc@12 # brew upgrade boost gcc git || true - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' @@ -56,7 +56,7 @@ jobs: - name: Check out repository if: always() - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 7590ae7a67..f5795822a4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -74,7 +74,7 @@ jobs: - name: Install Python if: always() - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" @@ -96,7 +96,7 @@ jobs: - name: Check out repository if: always() - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' From c47e7b7364a70c971a654116a3fec644c413f426 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 24 Feb 2024 19:39:31 +0100 Subject: [PATCH 134/159] [docs] Polishing the documentation - Remove defunct Twitter Account. - Fix dead links. - Replace system overview graphic. --- docs/mkdocs.yml | 2 - docs/src/how-modm-works.md | 7 +- docs/src/images/system-overview.dot | 31 ++++++++ docs/src/images/system-overview.svg | 108 ++++++++++++++++++++++++++++ docs/src/images/system_overview.png | Bin 164744 -> 0 bytes examples/README.md | 4 +- 6 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 docs/src/images/system-overview.dot create mode 100644 docs/src/images/system-overview.svg delete mode 100644 docs/src/images/system_overview.png diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 30455fdb8c..efcd4b7a43 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -49,8 +49,6 @@ extra: social: - icon: fontawesome/brands/github-alt link: https://github.com/modm-io - - icon: fontawesome/brands/twitter - link: https://twitter.com/modm_embedded markdown_extensions: - markdown.extensions.admonition diff --git a/docs/src/how-modm-works.md b/docs/src/how-modm-works.md index 159432a4fb..a1922b0d59 100644 --- a/docs/src/how-modm-works.md +++ b/docs/src/how-modm-works.md @@ -13,16 +13,15 @@ specifically for your targets and needs and generate a custom library. You can generate more than just code, in this example, lbuild also generates a build system which then compiles and links the application into a executable. - -![](images/system_overview.png) +
+![](images/system-overview.svg) +
We've also put a lot of thought into modm-devices, about what data to extract, how to format and store it. We automated the entire process to get the high quality data we use to build our library. You can read all [about modm-devices in this blog post](http://blog.salkinium.com/modm-devices). -![](http://blog.salkinium.com/assets/dfg_architecture.png) - ## modm is highly modular diff --git a/docs/src/images/system-overview.dot b/docs/src/images/system-overview.dot new file mode 100644 index 0000000000..f7beace77b --- /dev/null +++ b/docs/src/images/system-overview.dot @@ -0,0 +1,31 @@ +digraph dependencies +{ + bgcolor = transparent; + rankdir = LR; + subgraph modm + { + label = "modm"; + node [style=filled, shape=box]; + + modm_config [label="Config", style="filled,solid", fillcolor=lightsalmon]; + modm_application [label="Application", style="filled,solid", fillcolor=lightsalmon, rank=1]; + + modm_lbuild [label="lbuild", style="filled,solid", fillcolor=lightblue]; + modm_modules [label="modm", style="filled,solid,bold", fillcolor=lightblue]; + modm_devices [label="Database", style="filled,solid", fillcolor=lightblue]; + + modm_build_system [label="Build System", style="filled,solid", fillcolor=lightgreen]; + modm_custom_library [label="C++ Library", style="filled,solid", fillcolor=lightgreen]; + modm_build_artifacts [label="Build Artifacts", style="filled,solid", fillcolor=lightgreen]; + } + modm_config -> modm_lbuild [constraint=false]; + + modm_lbuild -> modm_modules; + modm_devices -> modm_modules [constraint=false]; + modm_modules -> modm_build_system; + modm_modules -> modm_custom_library; + + modm_application -> modm_build_system; + modm_custom_library -> modm_build_system [constraint=false]; + modm_build_system -> modm_build_artifacts; +} diff --git a/docs/src/images/system-overview.svg b/docs/src/images/system-overview.svg new file mode 100644 index 0000000000..cba84f3128 --- /dev/null +++ b/docs/src/images/system-overview.svg @@ -0,0 +1,108 @@ + + + + + + +dependencies + + +modm_config + +Config + + + +modm_lbuild + +lbuild + + + +modm_config->modm_lbuild + + + + + +modm_application + +Application + + + +modm_build_system + +Build System + + + +modm_application->modm_build_system + + + + + +modm_modules + +modm + + + +modm_lbuild->modm_modules + + + + + +modm_modules->modm_build_system + + + + + +modm_custom_library + +C++ Library + + + +modm_modules->modm_custom_library + + + + + +modm_devices + +Database + + + +modm_devices->modm_modules + + + + + +modm_build_artifacts + +Build Artifacts + + + +modm_build_system->modm_build_artifacts + + + + + +modm_custom_library->modm_build_system + + + + + diff --git a/docs/src/images/system_overview.png b/docs/src/images/system_overview.png deleted file mode 100644 index 8c2172fe430ab00aa1c67827347c6d7d93d80569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164744 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

!0-!`*G>LOEI}u8nA( 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

p}gTNTsE?$c{ZH-8!b8VV1Z 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

kLIfmR3-!R-W&w0 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 diff --git a/examples/README.md b/examples/README.md index cf042d228c..bdec775371 100644 --- a/examples/README.md +++ b/examples/README.md @@ -107,10 +107,10 @@ Here are some additional examples of displays and sensors we like: - [SSD1306 OLED display](https://github.com/modm-io/modm/blob/develop/examples/stm32f4_discovery/display/ssd1306/main.cpp): Draws text and graphics onto I2C display. - [BMP085/BMP180 barometer](https://github.com/modm-io/modm/blob/develop/examples/stm32f4_discovery/barometer_bmp085_bmp180/main.cpp): Reads atmospheric pressure and temperature from I2C sensor. -- [BMP180/BME280 barometer](https://github.com/modm-io/modm/tree/develop/examples/stm32f103c8t6_blue_pill/environment): Reads atmospheric pressure and temperature from multiple I2C sensors. +- [BMP180/BME280 barometer](https://github.com/modm-io/modm/tree/develop/examples/blue_pill/environment): Reads atmospheric pressure and temperature from multiple I2C sensors. - [VL6180 time-of-flight distance sensor](https://github.com/modm-io/modm/blob/develop/examples/stm32f4_discovery/distance_vl6180/main.cpp): Reads distance and ambient light from I2C sensor. - [VL53L0 time-of-flight distance sensor](https://github.com/modm-io/modm/tree/develop/examples/nucleo_f401re/distance_vl53l0/main.cpp): Much improved version of the VL6180 sensor. -- [ADNS9800 motion sensor](https://github.com/modm-io/modm/tree/develop/examples/stm32f103c8t6_blue_pill/adns_9800/main.cpp): Reads 2D motion from SPI sensor used in gaming mice. +- [ADNS9800 motion sensor](https://github.com/modm-io/modm/tree/develop/examples/blue_pill/adns_9800/main.cpp): Reads 2D motion from SPI sensor used in gaming mice. - [TCS3414 color sensor](https://github.com/modm-io/modm/blob/develop/examples/stm32f4_discovery/colour_tcs3414/main.cpp): Reads RGB color from I2C sensor. - [HD44780 over I2C-GPIO expander](https://github.com/modm-io/modm/blob/develop/examples/stm32f4_discovery/display/hd44780/main.cpp): Draws text via native GPIO port or I2C-GPIO expander port onto character display. From 009bb06f3830cd5ed5040fdd69c7e3633b0f0c1e Mon Sep 17 00:00:00 2001 From: Thomas Sommer Date: Thu, 19 Aug 2021 08:17:49 +0200 Subject: [PATCH 135/159] [board] Add DISCO-F411VE --- README.md | 29 ++-- examples/generic/usb/project.xml | 1 + src/modm/board/disco_f411ve/board.hpp | 231 ++++++++++++++++++++++++++ src/modm/board/disco_f411ve/board.xml | 14 ++ src/modm/board/disco_f411ve/module.lb | 46 +++++ 5 files changed, 307 insertions(+), 14 deletions(-) create mode 100644 src/modm/board/disco_f411ve/board.hpp create mode 100644 src/modm/board/disco_f411ve/board.xml create mode 100644 src/modm/board/disco_f411ve/module.lb diff --git a/README.md b/README.md index 3d6b9e4338..f549319882 100644 --- a/README.md +++ b/README.md @@ -624,73 +624,74 @@ We have out-of-box support for many development boards including documentation. DISCO-F303VC DISCO-F401VC DISCO-F407VG -DISCO-F429ZI +DISCO-F411VE +DISCO-F429ZI DISCO-F469NI DISCO-F746NG DISCO-F769NI -DISCO-L152RC +DISCO-L152RC DISCO-L476VG FEATHER-M0 FEATHER-M4 -FEATHER-RP2040 +FEATHER-RP2040 MEGA-2560-PRO NUCLEO-F031K6 NUCLEO-F042K6 -NUCLEO-F072RB +NUCLEO-F072RB NUCLEO-F091RC NUCLEO-F103RB NUCLEO-F303K8 -NUCLEO-F303RE +NUCLEO-F303RE NUCLEO-F334R8 NUCLEO-F401RE NUCLEO-F411RE -NUCLEO-F429ZI +NUCLEO-F429ZI NUCLEO-F439ZI NUCLEO-F446RE NUCLEO-F446ZE -NUCLEO-F746ZG +NUCLEO-F746ZG NUCLEO-F767ZI NUCLEO-G070RB NUCLEO-G071RB -NUCLEO-G431KB +NUCLEO-G431KB NUCLEO-G431RB NUCLEO-G474RE NUCLEO-H723ZG -NUCLEO-H743ZI +NUCLEO-H743ZI NUCLEO-L031K6 NUCLEO-L053R8 NUCLEO-L152RE -NUCLEO-L432KC +NUCLEO-L432KC NUCLEO-L452RE NUCLEO-L476RG NUCLEO-L496ZG-P -NUCLEO-L552ZE-Q +NUCLEO-L552ZE-Q NUCLEO-U575ZI-Q OLIMEXINO-STM32 Raspberry Pi Pico -SAMD21-MINI +SAMD21-MINI SAMD21-XPLAINED-PRO SAME54-XPLAINED-PRO SAME70-XPLAINED -SAMG55-XPLAINED-PRO +SAMG55-XPLAINED-PRO SAMV71-XPLAINED-ULTRA Smart Response XE STM32-F4VE -STM32F030-DEMO +STM32F030-DEMO THINGPLUS-RP2040 diff --git a/examples/generic/usb/project.xml b/examples/generic/usb/project.xml index 91e2aceba2..4abc6e9b9f 100644 --- a/examples/generic/usb/project.xml +++ b/examples/generic/usb/project.xml @@ -5,6 +5,7 @@ + diff --git a/src/modm/board/disco_f411ve/board.hpp b/src/modm/board/disco_f411ve/board.hpp new file mode 100644 index 0000000000..0e4b470a2b --- /dev/null +++ b/src/modm/board/disco_f411ve/board.hpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024, Thomas Sommer + * + * 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 + +using namespace modm::platform; + +namespace Board +{ +/// @ingroup modm_board_disco_f411ve +/// @{ +using namespace modm::literals; + +/// STM32F411 running at 96MHz generated from the external 8MHz crystal +struct SystemClock +{ + static constexpr uint32_t Frequency = 96_MHz; + static constexpr uint32_t Ahb = Frequency; + static constexpr uint32_t Apb1 = Frequency / 2; + static constexpr uint32_t Apb2 = Frequency; + + static constexpr uint32_t Adc = Apb2; + + static constexpr uint32_t Spi1 = Apb2; + static constexpr uint32_t Spi2 = Apb1; + static constexpr uint32_t Spi3 = Apb1; + static constexpr uint32_t Spi4 = Apb2; + static constexpr uint32_t Spi5 = Apb2; + + static constexpr uint32_t Usart1 = Apb2; + static constexpr uint32_t Usart2 = Apb1; + static constexpr uint32_t Usart3 = Apb1; + static constexpr uint32_t Uart4 = Apb1; + static constexpr uint32_t Uart5 = Apb1; + static constexpr uint32_t Usart6 = Apb2; + static constexpr uint32_t Uart7 = Apb1; + static constexpr uint32_t Uart8 = Apb1; + + static constexpr uint32_t I2c1 = Apb1; + static constexpr uint32_t I2c2 = Apb1; + static constexpr uint32_t I2c3 = Apb1; + + static constexpr uint32_t Apb1Timer = Apb1 * 2; + static constexpr uint32_t Apb2Timer = Apb2 * 2; + static constexpr uint32_t Timer1 = Apb2Timer; + static constexpr uint32_t Timer2 = Apb1Timer; + static constexpr uint32_t Timer3 = Apb1Timer; + static constexpr uint32_t Timer4 = Apb1Timer; + static constexpr uint32_t Timer5 = Apb1Timer; + static constexpr uint32_t Timer9 = Apb2Timer; + static constexpr uint32_t Timer10 = Apb2Timer; + static constexpr uint32_t Timer11 = Apb2Timer; + + static constexpr uint32_t Usb = 48_MHz; + + static bool inline enable() + { + Rcc::enableExternalCrystal(); // 8MHz + const Rcc::PllFactors pllFactors{ + .pllM = 7, // 8MHz / M=7 -> ~1.14MHz + .pllN = 336, // 1.14MHz * N=336 -> 384MHz + .pllP = 4, // 384MHz / P=4 -> 96MHz = F_cpu + .pllQ = 8, // 384MHz / P=8 -> 48MHz = F_usb + }; + Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); + // set flash latency for 100MHz + Rcc::setFlashLatency(); + // switch system clock to PLL output + Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); + Rcc::setAhbPrescaler(Rcc::AhbPrescaler::Div1); + // APB1 has max. 50MHz + // APB2 has max. 100MHz + Rcc::setApb1Prescaler(Rcc::Apb1Prescaler::Div2); + Rcc::setApb2Prescaler(Rcc::Apb2Prescaler::Div1); + // update frequencies for busy-wait delay functions + Rcc::updateCoreFrequency(); + + return true; + } +}; + +using LedUsb = GpioOutputA9; +using ClockOut = GpioOutputA8; +using SystemClockOut = GpioOutputC9; + +using Button = GpioInputA0; + +// 4 user colored LEDs aligned in a circle +using LedGreen = GpioOutputD12; +using LedOrange = GpioOutputD13; +using LedRed = GpioOutputD14; +using LedBlue = GpioOutputD15; +using Leds = SoftwareGpioPort; +/// @} + +namespace lis3 +{ +/// @ingroup modm_board_disco_f411ve +/// @{ +using Int = GpioInputE1; // LIS302DL_INT2 + +using Cs = GpioOutputE3; // LIS302DL_CS_I2C/SPI +using Sck = GpioOutputA5; // SPI1_SCK +using Mosi = GpioOutputA7; // SPI1_MOSI +using Miso = GpioInputA6; // SPI1_MISO + +using SpiMaster = SpiMaster1; +using Transport = modm::Lis3TransportSpi; +/// @} +} + + +namespace cs43 +{ +/// @ingroup modm_board_disco_f411ve +/// @{ +using Lrck = GpioOutputA4; // I2S3_WS +using Mclk = GpioOutputC7; // I2S3_MCK +using Sclk = GpioOutputC10; // I2S3_SCK +using Sdin = GpioOutputC12; // I2S3_SD + +using Reset = GpioOutputD4; // Audio_RST +using Scl = GpioB6; // Audio_SCL +using Sda = GpioB9; // Audio_SDA + +using I2cMaster = I2cMaster1; +// using I2sMaster = I2sMaster3; +/// @} +} + + +namespace mp45 +{ +/// @ingroup modm_board_disco_f411ve +/// @{ +using Clk = GpioOutputB10; // CLK_IN: I2S2_CK +using Dout = GpioInputC3; // PDM_OUT: I2S2_SD +// using I2sMaster = I2sMaster2; +/// @} +} + + +namespace usb +{ +/// @ingroup modm_board_disco_f411ve +/// @{ +using Vbus = GpioInputA9; // VBUS_FS: USB_OTG_HS_VBUS +using Id = GpioA10; // OTG_FS_ID: USB_OTG_FS_ID +using Dm = GpioA11; // OTG_FS_DM: USB_OTG_FS_DM +using Dp = GpioA12; // OTG_FS_DP: USB_OTG_FS_DP + +using Overcurrent = GpioInputD5; // OTG_FS_OverCurrent +using Power = GpioOutputC0; // OTG_FS_PowerSwitchOn + +using Device = UsbFs; +/// @} +} + +/// @ingroup modm_board_disco_f411ve +/// @{ +inline void +initialize() +{ + SystemClock::enable(); + SysTickTimer::initialize(); + + Leds::setOutput(modm::Gpio::Low); + LedUsb::setOutput(modm::Gpio::Low); + + Button::setInput(Gpio::InputType::Floating); +} + +inline void +initializeLis3() +{ + lis3::Int::setInput(); + lis3::Cs::setOutput(modm::Gpio::High); + + lis3::SpiMaster::connect(); + lis3::SpiMaster::initialize(); + lis3::SpiMaster::setDataMode(lis3::SpiMaster::DataMode::Mode3); +} + +/// not supported yet, due to missing I2S driver +inline void +initializeCs43() +{ + // cs43::Lrck::connect(cs43::I2sMaster::Ws); + // cs43::Mclk::connect(cs43::I2sMaster::Mck); + // cs43::Sclk::connect(cs43::I2sMaster::Ck); + // cs43::Sdin::connect(cs43::I2sMaster::Sd); + + cs43::Reset::setOutput(modm::Gpio::High); + + cs43::I2cMaster::connect(); + cs43::I2cMaster::initialize(); +} + +/// not supported yet, due to missing I2S driver +inline void +initializeMp45() +{ + // mp45::Clk::connect(mp45::I2sMaster::Ck); + // mp45::Dout::connect(mp45::I2sMaster::Sd); +} + +inline void +initializeUsbFs(uint8_t priority=3) +{ + usb::Device::initialize(priority); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; +} +/// @} +} diff --git a/src/modm/board/disco_f411ve/board.xml b/src/modm/board/disco_f411ve/board.xml new file mode 100644 index 0000000000..ee97f5e7f9 --- /dev/null +++ b/src/modm/board/disco_f411ve/board.xml @@ -0,0 +1,14 @@ + + + + ../../../../repo.lb + + + + + + + + modm:board:disco-f411ve + + diff --git a/src/modm/board/disco_f411ve/module.lb b/src/modm/board/disco_f411ve/module.lb new file mode 100644 index 0000000000..3d736c326f --- /dev/null +++ b/src/modm/board/disco_f411ve/module.lb @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024, Thomas Sommer +# +# 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:disco-f411ve" + module.description = """\ +# STM32F4DISCOVERY + +[Discovery kit for STM32F411](https://www.st.com/en/evaluation-tools/32f411ediscovery.html) +""" + +def prepare(module, options): + if not options[":target"].partname.startswith("stm32f411ve"): + return False + + module.depends( + ":architecture:clock", + ":driver:lis3dsh", + ":driver:lsm303a", + ":driver:l3gd20", + ":platform:clock", + ":platform:core", + ":platform:gpio", + ":platform:i2c:1", + ":platform:spi:1", + ":platform:usb:fs") + 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('.') + env.collect(":build:openocd.source", "board/stm32f4discovery.cfg"); From dbfd93b7490f5cc03ac636a0ff7147f247b68eea Mon Sep 17 00:00:00 2001 From: Henrik Hose Date: Wed, 6 Mar 2024 18:40:59 +0100 Subject: [PATCH 136/159] [driver] Adding AS5047 driver and example --- README.md | 29 ++-- examples/nucleo_g474re/as5047/main.cpp | 79 +++++++++++ examples/nucleo_g474re/as5047/project.xml | 14 ++ src/modm/driver/encoder/as5047.hpp | 157 ++++++++++++++++++++++ src/modm/driver/encoder/as5047.lb | 32 +++++ src/modm/driver/encoder/as5047_impl.hpp | 54 ++++++++ 6 files changed, 351 insertions(+), 14 deletions(-) create mode 100644 examples/nucleo_g474re/as5047/main.cpp create mode 100644 examples/nucleo_g474re/as5047/project.xml create mode 100644 src/modm/driver/encoder/as5047.hpp create mode 100644 src/modm/driver/encoder/as5047.lb create mode 100644 src/modm/driver/encoder/as5047_impl.hpp diff --git a/README.md b/README.md index f549319882..bdae9839ad 100644 --- a/README.md +++ b/README.md @@ -721,100 +721,101 @@ your specific needs. ADS816x AMS5915 APA102 +AS5047 AT24MAC402 -SPI Flash +SPI Flash BME280 BMI088 BMP085 BNO055 CAT24AA -CYCLE-COUNTER +CYCLE-COUNTER DRV832X DS1302 DS1631 DS18B20 EA-DOG -Encoder Input +Encoder Input Encoder Input BitBang Encoder Output BitBang FT245 FT6x06 Gpio Sampler -HCLAx +HCLAx HD44780 HMC58x HMC6343 HX711 I2C-EEPROM -ILI9341 +ILI9341 IS31FL3733 ITG3200 IXM42XXX L3GD20 LAN8720A -LAWICEL +LAWICEL LIS302DL LIS3DSH LIS3MDL LM75 LP503x -LSM303A +LSM303A LSM6DS33 LSM6DSO LTC2984 MAX31855 MAX31865 -MAX6966 +MAX6966 MAX7219 MCP23x17 MCP2515 MCP3008 MCP7941x -MCP990X +MCP990X MMC5603 MS5611 MS5837 NOKIA5110 NRF24 -TFT-DISPLAY +TFT-DISPLAY PAT9125EL PCA8574 PCA9535 PCA9548A PCA9685 -QMC5883L +QMC5883L SH1106 SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 -SSD1306 +SSD1306 ST7586S ST7789 STTS22H STUSB4500 SX1276 -SX128X +SX128X TCS3414 TCS3472 TLC594x TMP102 TMP12x -TMP175 +TMP175 TOUCH2046 VL53L0 VL6180 diff --git a/examples/nucleo_g474re/as5047/main.cpp b/examples/nucleo_g474re/as5047/main.cpp new file mode 100644 index 0000000000..d7feb52b31 --- /dev/null +++ b/examples/nucleo_g474re/as5047/main.cpp @@ -0,0 +1,79 @@ +// coding: utf-8 +/* + * Copyright (c) 2024, Henrik Hose + * + * 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 + +using SpiMaster = SpiMaster1; + +using Cs = modm::platform::GpioB10; +using Mosi = modm::platform::GpioB5; +using Miso = modm::platform::GpioB4; +using Sck = modm::platform::GpioB3; + +using namespace Board; +using namespace modm::literals; + +class EncoderThread : public modm::pt::Protothread +{ +public: + EncoderThread() : encoder(data) {} + + bool + run() + { + PT_BEGIN(); + + while (true) + { + PT_CALL(encoder.readout()); + + MODM_LOG_INFO << "\nNew readout:" << modm::endl; + MODM_LOG_INFO << " angle degree: " << data.getAngleDeg() << " degrees" << modm::endl; + MODM_LOG_INFO << " angle rad: " << data.getAngleRad() << " radians" << modm::endl; + MODM_LOG_INFO << " angle raw: " << data.getAngleRaw() << modm::endl; + + timeout.restart(std::chrono::milliseconds(500)); + PT_WAIT_UNTIL(timeout.isExpired()); + } + + PT_END(); + } + +private: + modm::as5047::Data data; + modm::As5047 encoder; + + modm::ShortTimeout timeout; +} encoderThread; + +int +main() +{ + Board::initialize(); + + Cs::setOutput(modm::Gpio::High); + + SpiMaster::connect(); + SpiMaster::initialize(); + + MODM_LOG_INFO << "==========AS5047 Test==========" << modm::endl; + MODM_LOG_DEBUG << "Debug logging here" << modm::endl; + MODM_LOG_INFO << "Info logging here" << modm::endl; + MODM_LOG_WARNING << "Warning logging here" << modm::endl; + MODM_LOG_ERROR << "Error logging here" << modm::endl; + MODM_LOG_INFO << "===============================" << modm::endl; + + while (true) { encoderThread.run(); } + + return 0; +} \ No newline at end of file diff --git a/examples/nucleo_g474re/as5047/project.xml b/examples/nucleo_g474re/as5047/project.xml new file mode 100644 index 0000000000..4b86fa2b25 --- /dev/null +++ b/examples/nucleo_g474re/as5047/project.xml @@ -0,0 +1,14 @@ + + modm:nucleo-g474re + + + + + modm:driver:as5047 + modm:platform:gpio + modm:platform:spi:1 + modm:processing:protothread + modm:processing:timer + modm:build:scons + + \ No newline at end of file diff --git a/src/modm/driver/encoder/as5047.hpp b/src/modm/driver/encoder/as5047.hpp new file mode 100644 index 0000000000..0f597a0f01 --- /dev/null +++ b/src/modm/driver/encoder/as5047.hpp @@ -0,0 +1,157 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2024, Henrik Hose + * + * 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_AS5047_HPP +#define MODM_AS5047_HPP + +#include +#include +#include +#include +#include +#include + +namespace modm +{ + +/// @cond +namespace detail +{ +constexpr uint16_t +as5047_setMsbToEvenParity(const uint16_t num) +{ + uint16_t par = 0x7fff & num; + par ^= par >> 8; + par ^= par >> 4; + par ^= par >> 2; + par ^= par >> 1; + return ((par & 1) << 15) | (0x7fff & num); +} +} // namespace detail +/// @endcond + +/// @ingroup modm_driver_as5047 +struct as5047 +{ + enum class Errorfl : uint16_t + { + Parerr = Bit2, + Invcomm = Bit1, + Frerr = Bit0, + }; + MODM_FLAGS16(Errorfl) + + enum class Prog : uint16_t + { + Progver = Bit6, + Progotp = Bit3, + Otpref = Bit2, + Progen = Bit0, + }; + MODM_FLAGS16(Prog) + + enum class Diaagc : uint16_t + { + Magl = Bit11, + Magh = Bit10, + Cof = Bit9, + Lf = Bit8, + }; + MODM_FLAGS16(Diaagc) + + enum class Register : uint16_t + { + ReadNop = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x0000)), + ReadErrfl = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x0001)), + ReadProg = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x0003)), + ReadDiaagc = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x3FFC)), + ReadMag = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x3FFD)), + ReadAngleunc = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x3FFE)), + ReadAnglecom = detail::as5047_setMsbToEvenParity(((1 << 14) | 0x3FFF)), + + }; + + struct modm_packed Data + { + /// @return + constexpr float + getAngleRad() const + { + const uint16_t angle = data & 0x3fff; + return static_cast(angle) / 16383.f * 2.f * std::numbers::pi_v; + } + + /// @return + constexpr float + getAngleDeg() const + { + const uint16_t angle = data & 0x3fff; + return static_cast(angle) / 16383.f * 360.f; + } + + /// @return + constexpr uint16_t + getAngleRaw() const + { + const uint16_t angle = data & 0x3fff; + return angle; + } + + uint16_t data; + }; +}; // struct as5047 + +/** + * @tparam SpiMaster + * @tparam Cs + * + * @author Henrik Hose + * @ingroup modm_driver_as5047 + */ +template +class As5047 : public as5047, public modm::SpiDevice, protected modm::NestedResumable<5> +{ +public: + using Data = as5047::Data; + + /** + * @param data pointer to buffer of the internal data of type Data + */ + As5047(Data &data); + + /// Call this function once before using the device + modm::ResumableResult + initialize(); + + /// Read the raw data from the sensor + modm::ResumableResult + readout(); + + /// Get the data object for this sensor + inline Data & + getData() + { + return data; + } + +private: + Data &data; + uint8_t inBuffer[2]; + uint8_t outBuffer[2]; +}; + +} // namespace modm + +#include "as5047_impl.hpp" + +#endif // MODM_AS5047_HPP \ No newline at end of file diff --git a/src/modm/driver/encoder/as5047.lb b/src/modm/driver/encoder/as5047.lb new file mode 100644 index 0000000000..a556ebdb74 --- /dev/null +++ b/src/modm/driver/encoder/as5047.lb @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024, Henrik Hose +# +# 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 = ":driver:as5047" + module.description = """\ +# AS5047 14 bit Absolute Encoder SPI Driver + +[Datasheet](https://ams.com/documents/20143/36005/AS5047D_DS000394_2-00.pdf) +""" + +def prepare(module, options): + module.depends( + ":architecture:gpio", + ":architecture:spi.device", + ":processing:resumable" + ) + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/encoder" + env.copy("as5047.hpp") + env.copy("as5047_impl.hpp") diff --git a/src/modm/driver/encoder/as5047_impl.hpp b/src/modm/driver/encoder/as5047_impl.hpp new file mode 100644 index 0000000000..df23d7187e --- /dev/null +++ b/src/modm/driver/encoder/as5047_impl.hpp @@ -0,0 +1,54 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2024, Henrik Hose + * + * 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_AS5047_HPP +#error "Don't include this file directly, use 'as5047.hpp' instead!" +#endif + +namespace modm +{ + +template +As5047::As5047(Data &data) : data(data) +{ + this->attachConfigurationHandler([]() { SpiMaster::setDataMode(SpiMaster::DataMode::Mode1); }); + Cs::setOutput(modm::Gpio::High); +} + +template +modm::ResumableResult +As5047::readout() +{ + RF_BEGIN(); + + RF_WAIT_UNTIL(this->acquireMaster()); + + Cs::reset(); + outBuffer[1] = static_cast(Register::ReadAngleunc); + outBuffer[0] = static_cast(static_cast(Register::ReadAngleunc) >> 8); + RF_CALL(SpiMaster::transfer(outBuffer, inBuffer, 2)); + Cs::set(); + + modm::delay_us(1); + + Cs::reset(); + outBuffer[1] = 0; + outBuffer[0] = 0; + RF_CALL(SpiMaster::transfer(outBuffer, inBuffer, 2)); + data.data = static_cast(inBuffer[1]) | (static_cast(inBuffer[0]) << 8); + + if (this->releaseMaster()) { Cs::set(); } + RF_END(); +} + +} // namespace modm \ No newline at end of file From 2406559c0b15cbd30e0dc2e9ed840ad2a5074e9c Mon Sep 17 00:00:00 2001 From: Victor Costa Date: Tue, 28 Mar 2023 17:49:36 +0200 Subject: [PATCH 137/159] [stm32] Extend Timer features: PWM modes and Comparator --- README.md | 2 +- src/modm/platform/comp/stm32/base.hpp.in | 5 +- src/modm/platform/comp/stm32/comp.hpp.in | 50 ++++++++++++++++--- src/modm/platform/comp/stm32/module.lb | 15 ++++-- src/modm/platform/dma/stm32/dma.hpp.in | 2 +- src/modm/platform/timer/stm32/advanced.cpp.in | 15 +++++- src/modm/platform/timer/stm32/advanced.hpp.in | 47 ++++++++++++++++- .../platform/timer/stm32/advanced_base.hpp.in | 7 +++ .../timer/stm32/general_purpose.cpp.in | 37 +++++++++++--- .../timer/stm32/general_purpose.hpp.in | 43 ++++++++++++++-- .../timer/stm32/general_purpose_base.hpp.in | 36 +++++++++++++ 11 files changed, 235 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index bdae9839ad..3769b3c1f5 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✕ ✕ -○ +✅ ✅ ○ ○ diff --git a/src/modm/platform/comp/stm32/base.hpp.in b/src/modm/platform/comp/stm32/base.hpp.in index 46443a8940..aca544793f 100644 --- a/src/modm/platform/comp/stm32/base.hpp.in +++ b/src/modm/platform/comp/stm32/base.hpp.in @@ -34,6 +34,9 @@ namespace modm::platform MediumSpeed = 0b01 << 2, //MediumSpeed2 = 0b10 << 2, UltraLowPower = 0b11 << 2, + {% elif driver.type in ["stm32-tsmc90_orcazero_cube"] -%} + HighSpeed = 0b00 << 2, + MediumSpeed = 0b01 << 2, {% endif -%} }; protected: @@ -50,7 +53,7 @@ namespace modm::platform protected: static constexpr uint32_t PolarityMask = 0b1 << 15; - {% if driver.type in ["stm32-v1.3", "stm32-tsmc90_cube"] -%} + {% if driver.type in ["stm32-v1.3", "stm32-tsmc90_cube", "stm32-tsmc90_orcazero_cube"] -%} public: enum class Hysteresis diff --git a/src/modm/platform/comp/stm32/comp.hpp.in b/src/modm/platform/comp/stm32/comp.hpp.in index 440871f77d..e0cb6d9ac2 100644 --- a/src/modm/platform/comp/stm32/comp.hpp.in +++ b/src/modm/platform/comp/stm32/comp.hpp.in @@ -108,6 +108,26 @@ namespace modm::platform GpioD15 = 0b110 << 4, GpioB12 = 0b111 << 4, {% endif -%} + {% elif target.family == "g0" -%} + Vref1Div4 = 0b0000 << 4, + Vref1Div2 = 0b0001 << 4, + Vref3Div4 = 0b0010 << 4, + Vref = 0b0011 << 4, + Dac1Ch1 = 0b0100 << 4, + Dac1Ch2 = 0b0101 << 4, + {% if id == 1 -%} + GpioB1 = 0b0110 << 4, + GpioC4 = 0b0111 << 4, + GpioA0 = 0b1000 << 4, + {% elif id == 2 -%} + GpioB3 = 0b0110 << 4, + GpioB7 = 0b0111 << 4, + GpioA2 = 0b1000 << 4, + {% elif id == 3 -%} + GpioB2 = 0b0110 << 4, + GpioC0 = 0b0111 << 4, + GpioE8 = 0b1000 << 4, + {% endif -%} {% endif -%} }; protected: @@ -117,6 +137,8 @@ namespace modm::platform static constexpr uint32_t InvertingInputMask = (0b111 << 4) | (0b11 << 22) | (0b11 << 25); {% elif target.family in ["g4"] -%} static constexpr uint32_t InvertingInputMask = (0b1111 << 4) | (0b11 << 22); + {% elif target.family in ["g0"] -%} + static constexpr uint32_t InvertingInputMask = (0b1111 << 4); {% endif -%} public: @@ -160,6 +182,20 @@ namespace modm::platform GpioB14 = 0b0 << 8, GpioD14 = 0b1 << 8, {% endif -%} + {% elif target.family in ["g0"] -%} + {% if id == 1 -%} + GpioC5 = 0b00 << 8, + GpioB2 = 0b01 << 8, + GpioA1 = 0b10 << 8, + {% elif id == 2 -%} + GpioB4 = 0b00 << 8, + GpioB6 = 0b01 << 8, + GpioA3 = 0b10 << 8, + {% elif id == 3 -%} + GpioB0 = 0b00 << 8, + GpioC1 = 0b01 << 8, + GpioE7 = 0b10 << 8, + {% endif -%} {% endif -%} }; protected: @@ -169,6 +205,8 @@ namespace modm::platform static constexpr uint32_t NonInvertingInputMask = 0b1 << 8; {% elif target.family in ["l4"] -%} static constexpr uint32_t NonInvertingInputMask = 0b11 << 7; + {% elif target.family in ["g0"] -%} + static constexpr uint32_t NonInvertingInputMask = 0b11 << 8; {% endif -%} {% if target.family == "f3" -%} @@ -227,7 +265,7 @@ namespace modm::platform static inline void initialize( InvertingInput n_in, - {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4"] -%} + {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4", "g0"] -%} NonInvertingInput p_in, {% endif -%} {% if target.family == "f3" -%} @@ -241,7 +279,7 @@ namespace modm::platform bool lock_comp = false) { setInvertingInput(n_in); - {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4"] -%} + {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4", "g0"] -%} setNonInvertingInput(p_in); {% endif -%} {% if target.family == "f3" -%} @@ -350,7 +388,7 @@ namespace modm::platform return static_cast(COMP{{ id }}->CSR & InvertingInputMask); } - {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4"] -%} + {% if (target.family == "f3" and id > 1) or target.family in ["l4", "g4", "g0"] -%} /** * \brief Selects what the non-inverting input is connected to. */ @@ -370,10 +408,10 @@ namespace modm::platform } {% endif -%} - {% if (target.family == "f3" and (id == 2 or id == 4 or id == 6)) or (target.family == "l4" and id == 2) -%} + {% if (target.family == "f3" and (id == 2 or id == 4 or id == 6)) or (target.family == "l4" and id == 2) or (target.family == "g0") -%} {% if target.family == "f3" -%} {% set windowmode = "WNDWEN" %} - {% elif target.family == "l4" -%} + {% elif target.family in ["l4", "g0"] -%} {% set windowmode = "WINMODE" %} {% endif -%} /** @@ -481,7 +519,7 @@ namespace modm::platform { {% if target.family == "f3" -%} return COMP{{ id }}->CSR & {{ csr }}OUT; - {% elif target.family in ["l4", "g4"] -%} + {% elif target.family in ["l4", "g4", "g0"] -%} return COMP{{ id }}->CSR & {{ csr }}VALUE; {% endif -%} } diff --git a/src/modm/platform/comp/stm32/module.lb b/src/modm/platform/comp/stm32/module.lb index 87aee6f45d..2cadd24df0 100644 --- a/src/modm/platform/comp/stm32/module.lb +++ b/src/modm/platform/comp/stm32/module.lb @@ -31,7 +31,7 @@ class Instance(Module): instance_id = int(self.instance) properties["id"] = instance_id properties["driver"] = driver - properties["csr"] = "COMP_CSR_" if device.identifier["family"] in ["l4", "g4"] else "COMP_CSR_COMPx" + properties["csr"] = "COMP_CSR_" if device.identifier["family"] in ["l4", "g4", "g0"] else "COMP_CSR_COMPx" properties["blanking_source"] = dict() if device.identifier["family"] in ["g4"]: properties["blanking_source"]["b001"] = ["Tim1Oc5", "Tim1Oc5", "Tim1Oc5", "Tim3Oc4", "Tim2Oc3", "Tim8Oc5", "Tim1Oc5"][instance_id - 1] @@ -49,6 +49,13 @@ class Instance(Module): properties["blanking_source"]["b001"] = "Tim1Oc5" properties["blanking_source"]["b010"] = "Tim2Oc3" properties["blanking_source"]["b011"] = "Tim3Oc3" + elif device.identifier["family"] in ["g0"]: + properties["blanking_source"]["b00000"] = None + properties["blanking_source"]["b00001"] = "Tim1Oc4" + properties["blanking_source"]["b00010"] = "Tim1Oc5" + properties["blanking_source"]["b00100"] = "Tim2Oc3" + properties["blanking_source"]["b01000"] = "Tim3Oc3" + properties["blanking_source"]["b10000"] = "Tim15Oc2" @@ -83,7 +90,7 @@ def prepare(module, options): "stm32-v3.4" "stm32-v3.6" """ - if not device.get_driver("comp")["type"] in ["stm32-v1.3", "stm32-tsmc90_cube", "stm32-tsmc90_g4_rockfish_cube"]: + if not device.get_driver("comp")["type"] in ["stm32-v1.3", "stm32-tsmc90_cube", "stm32-tsmc90_g4_rockfish_cube", "stm32-tsmc90_orcazero_cube"]: return False # Only some STM32F3 and STM32L4 @@ -95,6 +102,8 @@ def prepare(module, options): return False elif device.identifier["family"] == "g4": pass + elif device.identifier["family"] == "g0": + pass else: return False @@ -112,7 +121,7 @@ def build(env): properties = device.properties properties["target"] = device.identifier properties["driver"] = driver - properties["csr"] = "COMP_CSR_" if device.identifier["family"] in ["l4", "g4"] else "COMP_CSR_COMPx" + properties["csr"] = "COMP_CSR_" if device.identifier["family"] in ["l4", "g4", "g0"] else "COMP_CSR_COMPx" env.substitutions = properties env.outbasepath = "modm/src/modm/platform/comp" diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/dma.hpp.in index 265f1954ba..4a8e895a58 100644 --- a/src/modm/platform/dma/stm32/dma.hpp.in +++ b/src/modm/platform/dma/stm32/dma.hpp.in @@ -452,7 +452,7 @@ public: static constexpr std::array muxChannels = { %% for channel in dma["mux-channels"][0]["mux-channel"] - MuxChannel({{ channel.position }}, {{ channel["dma-instance"] }}, {{ channel["dma-channel"] }}){{ "," if not loop.last }} + MuxChannel({{ channel.position }}, {{ channel["dma-instance"] }}, {{ channel["dma-channel"] }}){{ "," if not loop.last else '' }} %% endfor }; %% endif diff --git a/src/modm/platform/timer/stm32/advanced.cpp.in b/src/modm/platform/timer/stm32/advanced.cpp.in index e54e7dcfa5..41ac2b5ea7 100644 --- a/src/modm/platform/timer/stm32/advanced.cpp.in +++ b/src/modm/platform/timer/stm32/advanced.cpp.in @@ -167,7 +167,7 @@ modm::platform::Timer{{ id }}::configureInputChannel(uint32_t channel, // ---------------------------------------------------------------------------- void modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, - OutputCompareMode mode, uint16_t compareValue, PinState out) + OutputCompareMode mode, Value compareValue, PinState out) { channel -= 1; // 1..4 -> 0..3 @@ -216,6 +216,19 @@ modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, } } +void +modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, +OutputCompareMode mode, Value compareValue, +PinState out, OutputComparePolarity polarity, +PinState out_n, OutputComparePolarity polarity_n, +OutputComparePreload preload) +{ + // disable output + TIM{{ id }}->CCER &= ~(0xf << ((channel-1) * 4)); + setCompareValue(channel, compareValue); + configureOutputChannel(channel, mode, out, polarity, out_n, polarity_n, preload); +} + void modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, OutputCompareMode mode, diff --git a/src/modm/platform/timer/stm32/advanced.hpp.in b/src/modm/platform/timer/stm32/advanced.hpp.in index 7884521bf5..bdff947ae2 100644 --- a/src/modm/platform/timer/stm32/advanced.hpp.in +++ b/src/modm/platform/timer/stm32/advanced.hpp.in @@ -260,6 +260,32 @@ public: { return (TIM{{ id }}->BDTR & TIM_BDTR_MOE); } +%% if target.family in ["g0"] + static inline void + enableBreakInput() + { + TIM{{ id }}->AF1 |= TIM{{ id }}_AF1_BKINE; + } + + static inline void + disableBreakInput() + { + TIM{{ id }}->AF1 &= ~(TIM{{ id }}_AF1_BKINE); + } + + static inline void + setBreakPolarity(BreakInputPolarity polarity) + { + if (BreakInputPolarity::ActiveLow == polarity) + { + TIM{{ id }}->BDTR &= ~(TIM_BDTR_BKP); + } + else + { + TIM{{ id }}->BDTR |= TIM_BDTR_BKP; + } + } +%% endif /* * Enable/Disable automatic set of MOE bit at the next update event @@ -357,7 +383,7 @@ public: } static inline void - setRepetitionCount(uint8_t repetitionCount) + setRepetitionCount(uint16_t repetitionCount) { TIM{{ id }}->RCR = repetitionCount; } @@ -407,6 +433,25 @@ public: configureOutputChannel(channel, mode, compareValue, out); } + static void + configureOutputChannel(uint32_t channel, OutputCompareMode mode, + Value compareValue, PinState out, + OutputComparePolarity polarity, PinState out_n, + OutputComparePolarity polarity_n = OutputComparePolarity::ActiveHigh, + OutputComparePreload preload = OutputComparePreload::Disable); + + template + static void + configureOutputChannel(OutputCompareMode mode, + Value compareValue, PinState out, + OutputComparePolarity polarity, PinState out_n, + OutputComparePolarity polarity_n = OutputComparePolarity::ActiveHigh, + OutputComparePreload preload = OutputComparePreload::Disable) + { + constexpr auto channel = signalToChannel(); + configureOutputChannel(channel, mode, compareValue, out, polarity, out_n, polarity_n, preload); + } + /* * Configure Output Channel without changing the Compare Value * diff --git a/src/modm/platform/timer/stm32/advanced_base.hpp.in b/src/modm/platform/timer/stm32/advanced_base.hpp.in index fb65edb7c1..70d6f72c5b 100644 --- a/src/modm/platform/timer/stm32/advanced_base.hpp.in +++ b/src/modm/platform/timer/stm32/advanced_base.hpp.in @@ -122,6 +122,13 @@ public: Reset = 0, Set = TIM_CR2_OIS1, }; +%% if target.family in ["g0"] + enum class BreakInputPolarity : uint32_t + { + ActiveLow = 0, + ActiveHigh = 1, + }; +%% endif }; } // namespace platform diff --git a/src/modm/platform/timer/stm32/general_purpose.cpp.in b/src/modm/platform/timer/stm32/general_purpose.cpp.in index 3c1f8a62b3..1649913dd1 100644 --- a/src/modm/platform/timer/stm32/general_purpose.cpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.cpp.in @@ -203,7 +203,18 @@ modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, } } -%% if id in [15, 16, 17] +void +modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, +OutputCompareMode mode, Value compareValue, +PinState out, OutputComparePolarity polarity, +OutputComparePreload preload) +{ + // disable output + TIM{{ id }}->CCER &= ~(0xf << ((channel-1) * 4)); + setCompareValue(channel, compareValue); + configureOutputChannel(channel, mode, out, polarity, PinState::Disable, OutputComparePolarity::ActiveHigh, preload); +} + void modm::platform::Timer{{ id }}::configureOutputChannel(uint32_t channel, OutputCompareMode mode, @@ -211,21 +222,34 @@ PinState out, OutputComparePolarity polarity, PinState out_n, OutputComparePolarity polarity_n, OutputComparePreload preload) { +%% if id in [15, 16, 17] modm_assert(channel == 1, "Timer{{ id }}", "This timer has complementary output only on channel 1!", "{{ id }}"); +%% endif - channel -= 1; + channel -= 1; // 1..4 -> 0..3 // disable output TIM{{ id }}->CCER &= ~(0xf << (channel * 4)); uint32_t flags = static_cast(mode) | static_cast(preload); - const uint32_t offset = 8 * channel; + if (channel <= 1) + { + const uint32_t offset = 8 * channel; + + flags <<= offset; + flags |= TIM{{ id }}->CCMR1 & ~(0xff << offset); + + TIM{{ id }}->CCMR1 = flags; + } + else { + const uint32_t offset = 8 * (channel - 2); - flags <<= offset; - flags |= TIM{{ id }}->CCMR1 & ~(0xff << offset); + flags <<= offset; + flags |= TIM{{ id }}->CCMR2 & ~(0xff << offset); - TIM{{ id }}->CCMR1 = flags; + TIM{{ id }}->CCMR2 = flags; + } // CCER Flags (Enable/Polarity) flags = (static_cast(polarity_n) << 2) | @@ -234,7 +258,6 @@ OutputComparePreload preload) TIM{{ id }}->CCER |= flags << (channel * 4); } -%% endif // ---------------------------------------------------------------------------- void diff --git a/src/modm/platform/timer/stm32/general_purpose.hpp.in b/src/modm/platform/timer/stm32/general_purpose.hpp.in index 3e40462598..c401847d58 100644 --- a/src/modm/platform/timer/stm32/general_purpose.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose.hpp.in @@ -257,6 +257,14 @@ public: TIM{{ id }}->CNT = value; } +%% if id in [15, 16, 17] + static inline void + setRepetitionCount(uint8_t repetitionCount) + { + TIM{{ id }}->RCR = repetitionCount; + } +%% endif + %% if target.family not in ["l0", "l1"] and id in [15, 16, 17] static inline bool @@ -433,7 +441,35 @@ public: configureOutputChannel(channel, mode, compareValue, out, enableComparePreload); } -%% if id in [15, 16, 17] + static void + configureOutputChannel(uint32_t channel, OutputCompareMode mode, + Value compareValue, PinState out, + OutputComparePolarity polarity, + OutputComparePreload preload = OutputComparePreload::Disable); + + template + static void + configureOutputChannel(OutputCompareMode mode, + Value compareValue, PinState out, + OutputComparePolarity polarity, + OutputComparePreload preload = OutputComparePreload::Disable) + { + constexpr auto channel = signalToChannel(); + configureOutputChannel(channel, mode, compareValue, out, polarity, PinState::Disable, OutputComparePolarity::ActiveHigh, preload); + } + + /* + * Configure Output Channel without changing the Compare Value + * + * Normally used to reconfigure the Output channel without touching + * the compare value. This can e.g. be useful for commutation of a + * bldc motor. + * + * This function probably won't be used for a one time setup but + * rather for adjusting the output setting periodically. + * Therefore it aims to provide the best performance possible + * without sacrificing code readability. + */ static void configureOutputChannel(uint32_t channel, OutputCompareMode mode, PinState out, OutputComparePolarity polarity, @@ -450,10 +486,11 @@ public: OutputComparePreload preload = OutputComparePreload::Disable) { constexpr auto channel = signalToChannel(); - static_assert(channel == 1, "Timer{{ id }} has complementary output only on channel 1"); +%% if id in [15, 16, 17] + static_assert(channel == 1, "Timer{{ id }} has complementary output only on channel 1"); +%% endif configureOutputChannel(channel, mode, out, polarity, out_n, polarity_n, preload); } -%% endif /// Switch to Pwm Mode 2 /// diff --git a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in index 810aa10882..b8ddcaf9c4 100644 --- a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in @@ -141,6 +141,42 @@ public: * capture/compare register, else inactive. */ Pwm2 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0, + +#ifdef TIM_CCMR1_OC1M_3 + /** + * Combined PWM mode 1. + * + * OC1REF has the same behavior as in PWM mode 1. + * OC1REFC is the logical OR between OC1REF and OC2REF. + */ + Combined1 = TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_2, + + /** + * Combined PWM mode 2. + * + * OC1REF has the same behavior as in PWM mode 2. + * OC1REFC is the logical AND between OC1REF and OC2REF. + */ + Combined2 = TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0, + + /** + * Asymmetric PWM mode 1. + * + * OC1REF has the same behavior as in PWM mode 1. + * OC1REFC outputs OC1REF when the counter is counting up, + * OC2REF when it is counting down. + */ + Asymmetric1 = TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1, + + /** + * Asymmetric PWM mode 2. + * + * OC1REF has the same behavior as in PWM mode 2. + * OC1REFC outputs OC1REF when the counter is counting up, + * OC2REF when it is counting down. + */ + Asymmetric2 = TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0, +#endif }; MODM_FLAGS32(OutputCompareMode); From a371df6296cace3d5c177f1e2713898d7bf5e3d0 Mon Sep 17 00:00:00 2001 From: Victor Costa Date: Mon, 26 Feb 2024 17:47:18 +0100 Subject: [PATCH 138/159] [stm32] add enable break in timer --- src/modm/platform/timer/stm32/advanced.hpp.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modm/platform/timer/stm32/advanced.hpp.in b/src/modm/platform/timer/stm32/advanced.hpp.in index bdff947ae2..6e5741ba5c 100644 --- a/src/modm/platform/timer/stm32/advanced.hpp.in +++ b/src/modm/platform/timer/stm32/advanced.hpp.in @@ -261,6 +261,18 @@ public: return (TIM{{ id }}->BDTR & TIM_BDTR_MOE); } %% if target.family in ["g0"] + static inline void + enableBreak() + { + TIM{{ id }}->BDTR |= TIM_BDTR_BKE; + } + + static inline void + disableBreak() + { + TIM{{ id }}->BDTR &= ~(TIM_BDTR_BKE); + } + static inline void enableBreakInput() { From 3aedcf89254cd600c75561a0e10fe1dc8af8210b Mon Sep 17 00:00:00 2001 From: Thomas Sommer Date: Thu, 7 Mar 2024 22:56:21 +0100 Subject: [PATCH 139/159] fix clock in lis3::SpiMaster::initialize --- src/modm/board/disco_f411ve/board.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/board/disco_f411ve/board.hpp b/src/modm/board/disco_f411ve/board.hpp index 0e4b470a2b..ffb5286710 100644 --- a/src/modm/board/disco_f411ve/board.hpp +++ b/src/modm/board/disco_f411ve/board.hpp @@ -188,7 +188,7 @@ initializeLis3() lis3::Cs::setOutput(modm::Gpio::High); lis3::SpiMaster::connect(); - lis3::SpiMaster::initialize(); + lis3::SpiMaster::initialize(); lis3::SpiMaster::setDataMode(lis3::SpiMaster::DataMode::Mode3); } From 553c4c79226361f90911e84d9aa4c53c7f1acc36 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Mon, 24 Apr 2023 23:49:17 +0200 Subject: [PATCH 140/159] [cmake] Change template to respect repo name --- .../cmake/resources/CMakeLists.txt.in | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in index d1aba892e9..24e110abab 100644 --- a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in +++ b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in @@ -22,7 +22,7 @@ set_source_files_properties( %% endif %% if sources | length -add_library(modm OBJECT +add_library({{ repo }} OBJECT %% for file, flags in sources | sort {{ file | relocate | modm.posixify }} %% endfor @@ -37,14 +37,14 @@ add_library(modm OBJECT %% endif -add_library(modm_warnings INTERFACE) -add_library(modm_options INTERFACE) -add_library(modm_arch_options INTERFACE) +add_library({{ repo }}_warnings INTERFACE) +add_library({{ repo }}_options INTERFACE) +add_library({{ repo }}_arch_options INTERFACE) -modm_target_config_create(modm modm_arch_options modm_options modm_warnings) +modm_target_config_create({{ repo }} {{ repo }}_arch_options {{ repo }}_options {{ repo }}_warnings) %% if include_paths | length -target_include_directories(modm SYSTEM PUBLIC +target_include_directories({{ repo }} SYSTEM PUBLIC %% for path in include_paths | sort {{ path | relocate | modm.posixify }} %% endfor @@ -52,10 +52,10 @@ target_include_directories(modm SYSTEM PUBLIC %% endif -target_link_libraries(modm +target_link_libraries({{ repo }} PUBLIC - modm_arch_options + {{ repo }}_arch_options PRIVATE project_options - modm_options - modm_warnings) + {{ repo }}_options + {{ repo }}_warnings) From d257401eaa664d04abd21bc635aef151ea832f68 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Wed, 20 Sep 2023 02:21:42 +0200 Subject: [PATCH 141/159] [cmake] Support header only libraries --- .../cmake/resources/CMakeLists.txt.in | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in index 24e110abab..3727cc6442 100644 --- a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in +++ b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in @@ -27,7 +27,8 @@ add_library({{ repo }} OBJECT {{ file | relocate | modm.posixify }} %% endfor ) - +%% else +add_library({{ repo }} INTERFACE ) %% endif %% if per_file_attr | length @@ -44,7 +45,12 @@ add_library({{ repo }}_arch_options INTERFACE) modm_target_config_create({{ repo }} {{ repo }}_arch_options {{ repo }}_options {{ repo }}_warnings) %% if include_paths | length -target_include_directories({{ repo }} SYSTEM PUBLIC +target_include_directories({{ repo }} SYSTEM +%% if sources | length + PUBLIC +%% else + INTERFACE +%% endif %% for path in include_paths | sort {{ path | relocate | modm.posixify }} %% endfor @@ -52,10 +58,18 @@ target_include_directories({{ repo }} SYSTEM PUBLIC %% endif + target_link_libraries({{ repo }} +%% if sources | length PUBLIC +%% else + INTERFACE +%%endif {{ repo }}_arch_options +%% if sources | length PRIVATE project_options {{ repo }}_options - {{ repo }}_warnings) + {{ repo }}_warnings +%% endif + ) From 2f51822304b57467c74c9e986cbb394ea2e3bb29 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Fri, 22 Sep 2023 03:12:09 +0200 Subject: [PATCH 142/159] [cmake] Correctly propagate options to all repos --- .../cmake/resources/CMakeLists.txt.in | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in index 3727cc6442..5a9b99458d 100644 --- a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in +++ b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in @@ -9,7 +9,10 @@ # This file was autogenerated by the modm cmake builder. Do not modify! cmake_minimum_required(VERSION 3.15) +%% if is_modm include(cmake/ModmConfiguration.cmake) +%% endif + %% if asm_sources | length set_source_files_properties( @@ -38,11 +41,13 @@ add_library({{ repo }} INTERFACE ) %% endif -add_library({{ repo }}_warnings INTERFACE) -add_library({{ repo }}_options INTERFACE) -add_library({{ repo }}_arch_options INTERFACE) +%% if is_modm +add_library(modm_warnings INTERFACE) +add_library(modm_options INTERFACE) +add_library(modm_arch_options INTERFACE) -modm_target_config_create({{ repo }} {{ repo }}_arch_options {{ repo }}_options {{ repo }}_warnings) +modm_target_config_create(modm modm_arch_options modm_options modm_warnings) +%% endif %% if include_paths | length target_include_directories({{ repo }} SYSTEM @@ -59,17 +64,18 @@ target_include_directories({{ repo }} SYSTEM %% endif + target_link_libraries({{ repo }} %% if sources | length PUBLIC %% else INTERFACE %%endif - {{ repo }}_arch_options + modm_arch_options %% if sources | length PRIVATE project_options - {{ repo }}_options - {{ repo }}_warnings + modm_options + modm_warnings %% endif ) From 97ffcb414e8597198f26373b411c065d6447a162 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Fri, 22 Sep 2023 03:13:02 +0200 Subject: [PATCH 143/159] [cmake] Emulate scons behaviour of including everything --- tools/build_script_generator/cmake/module.lb | 5 +++++ .../cmake/resources/CMakeLists.txt.in | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/tools/build_script_generator/cmake/module.lb b/tools/build_script_generator/cmake/module.lb index a3f4495f61..175e216f9e 100644 --- a/tools/build_script_generator/cmake/module.lb +++ b/tools/build_script_generator/cmake/module.lb @@ -142,6 +142,7 @@ def post_build(env): files = [] per_file_attr = [] repo_filter = lambda scope: scope.repository == repo + inv_repo_filter = lambda scope: scope.repository != repo repo_flags = env.query("::collect_flags")(env, repo_filter) warning_pattern = re.compile("^-W") @@ -174,6 +175,9 @@ def post_build(env): files.append( (sfile, profiles) ) include_paths = env.collector_values("::path.include", filterfunc=repo_filter) + private_include_paths = env.collector_values( + "::path.include", filterfunc=inv_repo_filter + ) libary_paths = env.collector_values("::path.library", filterfunc=repo_filter) libaries = env.collector_values("::library", filterfunc=repo_filter) packages = env.collector_values("::pkg-config", filterfunc=repo_filter) @@ -188,6 +192,7 @@ def post_build(env): "libraries": libaries, "library_paths": libary_paths, "include_paths": include_paths, + "private_include_paths": private_include_paths, "packages": packages, "is_modm": repo == "modm", "per_file_attr": file_attrs, diff --git a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in index 5a9b99458d..61fb1de034 100644 --- a/tools/build_script_generator/cmake/resources/CMakeLists.txt.in +++ b/tools/build_script_generator/cmake/resources/CMakeLists.txt.in @@ -59,6 +59,12 @@ target_include_directories({{ repo }} SYSTEM %% for path in include_paths | sort {{ path | relocate | modm.posixify }} %% endfor +%% if sources | length + PRIVATE +%% for path in private_include_paths | sort + {{ path | relocate | modm.posixify }} +%% endfor +%% endif ) %% endif From a5cee07c453cb9c9e192bd3d0138bb13d02345a3 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Thu, 28 Sep 2023 00:58:30 +0200 Subject: [PATCH 144/159] [cmake] Fix build on windows --- .../resources/ModmConfiguration.cmake.in | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tools/build_script_generator/cmake/resources/ModmConfiguration.cmake.in b/tools/build_script_generator/cmake/resources/ModmConfiguration.cmake.in index 9a0b5de77b..1563ed72f0 100644 --- a/tools/build_script_generator/cmake/resources/ModmConfiguration.cmake.in +++ b/tools/build_script_generator/cmake/resources/ModmConfiguration.cmake.in @@ -9,8 +9,6 @@ # This file was autogenerated by the modm cmake builder. Do not modify! -find_program(PYTHON3_EXECUTABLE python3 REQUIRED) - # This function will prevent in-source builds function(assure_out_of_source_builds) # make sure the user doesn't play dirty with symlinks @@ -164,6 +162,8 @@ set({{ name | upper }}{{ "_" ~ (profile | upper) if profile | length else "" }} endfunction() function(modm_targets_create project_name) + find_package(Python3 COMPONENTS Interpreter REQUIRED) + set_target_properties(${project_name} PROPERTIES SUFFIX ".elf") @@ -175,40 +175,40 @@ function(modm_targets_create project_name) %% if core.startswith("cortex-m") add_custom_command(TARGET ${project_name} POST_BUILD - COMMAND PYTHONPATH=${PROJECT_SOURCE_DIR}/modm ${PYTHON3_EXECUTABLE} -m modm_tools.size ${project_name}.elf \"{{ memories }}\") + COMMAND cmake -E env PYTHONPATH=${PROJECT_SOURCE_DIR}/modm ${Python3_EXECUTABLE} -m modm_tools.size ${project_name}.elf \"{{ memories }}\") %% endif add_custom_target(size DEPENDS ${project_name}.elf) add_custom_command(TARGET size USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.size ${PROJECT_BINARY_DIR}/src/${project_name}.elf \"{{ memories }}\" + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.size ${PROJECT_BINARY_DIR}/src/${project_name}.elf \"{{ memories }}\" WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(program DEPENDS ${project_name}.elf) add_custom_command(TARGET program USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.openocd -f modm/openocd.cfg + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.openocd -f modm/openocd.cfg ${PROJECT_BINARY_DIR}/src/${project_name}.elf WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(program-bmp DEPENDS ${project_name}.elf) add_custom_command(TARGET program-bmp USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.bmp -p ${MODM_BMP_PORT} + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.bmp -p ${MODM_BMP_PORT} ${PROJECT_BINARY_DIR}/src/${project_name}.elf WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(program-jlink DEPENDS ${project_name}.elf) add_custom_command(TARGET program-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.jlink -device {{ jlink_partname }} + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.jlink -device {{ jlink_partname }} ${PROJECT_BINARY_DIR}/src/${project_name}.elf WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(debug DEPENDS ${project_name}.elf) add_custom_command(TARGET debug USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_gdbinit + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_gdbinit --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} openocd -f modm/openocd.cfg WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -216,7 +216,7 @@ function(modm_targets_create project_name) add_custom_target(debug-bmp DEPENDS ${project_name}.elf) add_custom_command(TARGET debug-bmp USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_bmp + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_bmp --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} bmp -p ${MODM_BMP_PORT} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -224,7 +224,7 @@ function(modm_targets_create project_name) add_custom_target(debug-jlink DEPENDS ${project_name}.elf) add_custom_command(TARGET debug-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_jlink + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -x modm/openocd_jlink --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} jlink -device {{ jlink_partname }} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -232,7 +232,7 @@ function(modm_targets_create project_name) add_custom_target(debug-coredump DEPENDS ${project_name}.elf) add_custom_command(TARGET debug-coredump USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} crashdebug WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -240,7 +240,7 @@ function(modm_targets_create project_name) add_custom_target(coredump) add_custom_command(TARGET coredump USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -ex "modm_coredump" -ex "modm_build_id" -ex "quit" openocd -f modm/openocd.cfg WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -248,7 +248,7 @@ function(modm_targets_create project_name) add_custom_target(coredump-bmp) add_custom_command(TARGET coredump-bmp USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -ex "modm_coredump" -ex "modm_build_id" -ex "quit" bmp -p ${MODM_BMP_PORT} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -256,7 +256,7 @@ function(modm_targets_create project_name) add_custom_target(coredump-jlink) add_custom_command(TARGET coredump-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.gdb -x modm/gdbinit -ex "modm_coredump" -ex "modm_build_id" -ex "quit" jlink -device {{ jlink_partname }} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -264,43 +264,43 @@ function(modm_targets_create project_name) add_custom_target(reset) add_custom_command(TARGET reset USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.openocd -f modm/openocd.cfg --reset + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.openocd -f modm/openocd.cfg --reset WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(reset-bmp) add_custom_command(TARGET reset-bmp USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.bmp -p ${MODM_BMP_PORT} --reset + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.bmp -p ${MODM_BMP_PORT} --reset WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(reset-jlink) add_custom_command(TARGET reset-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.jlink -device {{ jlink_partname }} --reset + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.jlink -device {{ jlink_partname }} --reset WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(log-itm) add_custom_command(TARGET log-itm USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.itm openocd -f modm/openocd.cfg --fcpu ${MODM_ITM_FCPU} + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.itm openocd -f modm/openocd.cfg --fcpu ${MODM_ITM_FCPU} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(log-itm-jlink) add_custom_command(TARGET log-itm-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.itm jlink -device {{ jlink_partname }} + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.itm jlink -device {{ jlink_partname }} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(log-rtt) add_custom_command(TARGET log-rtt USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.rtt --channel ${MODM_RTT_CHANNEL} openocd -f modm/openocd.cfg + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.rtt --channel ${MODM_RTT_CHANNEL} openocd -f modm/openocd.cfg WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(log-rtt-jlink) add_custom_command(TARGET log-rtt-jlink USES_TERMINAL - COMMAND PYTHONPATH=modm ${PYTHON3_EXECUTABLE} -m modm_tools.rtt --channel ${MODM_RTT_CHANNEL} jlink -device {{ jlink_partname }} + COMMAND cmake -E env PYTHONPATH=modm ${Python3_EXECUTABLE} -m modm_tools.rtt --channel ${MODM_RTT_CHANNEL} jlink -device {{ jlink_partname }} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) endfunction() From 1a89fbe6029ffd80f44bcb7308f685099586c390 Mon Sep 17 00:00:00 2001 From: Michael Jossen Date: Fri, 8 Mar 2024 01:11:43 +0000 Subject: [PATCH 145/159] [cmake] Update auto-generated CMakeLists --- .../cmake/resources/ci_CMakeLists.txt.in | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/build_script_generator/cmake/resources/ci_CMakeLists.txt.in b/tools/build_script_generator/cmake/resources/ci_CMakeLists.txt.in index cf68aeea18..9b689a7d8c 100644 --- a/tools/build_script_generator/cmake/resources/ci_CMakeLists.txt.in +++ b/tools/build_script_generator/cmake/resources/ci_CMakeLists.txt.in @@ -47,8 +47,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_library(project_options INTERFACE) -# Compile modm -add_subdirectory(modm) +# Compile libraries +%% for repo in generated_paths | sort +add_subdirectory({{ repo }}) +%% endfor # Find application sources: Never do this, file changes are not detected by make. file(GLOB APP_SOURCE_FILES *.c *.cpp */*.c */*.cpp) @@ -57,9 +59,11 @@ add_executable(${CMAKE_PROJECT_NAME} ${APP_SOURCE_FILES}) target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC include) target_link_libraries(${CMAKE_PROJECT_NAME} +%% for repo in generated_paths | sort + {{ repo }} +%% endfor modm_options - modm_warnings - modm) + modm_warnings) # Outputs hex and bin files. modm_targets_create(${CMAKE_PROJECT_NAME}) From 9496060e0e05d228b12d74c3d32311eabeb719fb Mon Sep 17 00:00:00 2001 From: Victor Costa Date: Mon, 26 Feb 2024 17:24:52 +0100 Subject: [PATCH 146/159] [stm32] add advanced initialize on iwdg --- src/modm/platform/iwdg/stm32/iwdg.hpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/modm/platform/iwdg/stm32/iwdg.hpp b/src/modm/platform/iwdg/stm32/iwdg.hpp index 26321b6c5a..59365c8d3f 100644 --- a/src/modm/platform/iwdg/stm32/iwdg.hpp +++ b/src/modm/platform/iwdg/stm32/iwdg.hpp @@ -46,6 +46,15 @@ class Iwdg : public ::modm::PeripheralDriver }; public: + static inline void + initialize(Prescaler prescaler, uint16_t reload) + { + writeKey(writeCommand); + IWDG->PR = uint32_t(prescaler); + IWDG->RLR = reload; + writeKey(0); // disable access to PR and RLR registers + } + template< class SystemClock, milliseconds_t timeout, percent_t tolerance=pct(1) > static void initialize() @@ -55,10 +64,9 @@ class Iwdg : public ::modm::PeripheralDriver SystemClock::Iwdg, frequency, 1ul << 12, 256, 4); assertDurationInTolerance< 1.0 / result.frequency, 1.0 / frequency, tolerance >(); - configure(Prescaler(result.index), result.counter - 1); + initialize(Prescaler(result.index), result.counter - 1); } - static inline void enable() { @@ -78,15 +86,6 @@ class Iwdg : public ::modm::PeripheralDriver } private: - static inline void - configure(Prescaler prescaler, uint16_t reload) - { - writeKey(writeCommand); - IWDG->PR = uint32_t(prescaler); - IWDG->RLR = reload; - writeKey(0); // disable access to PR and RLR registers - } - static inline void writeKey(uint16_t key) { From 734de07d686a5301081d58d97216ea0d9dab823a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Waldh=C3=A4usl?= Date: Wed, 4 Oct 2023 05:03:12 -0700 Subject: [PATCH 147/159] [stm32] i2c irq priority can be set --- src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in | 10 +++++----- src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in index c8c1e50bb0..a32d34b08b 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in @@ -513,7 +513,7 @@ MODM_ISR(I2C{{ id }}_ER) // ---------------------------------------------------------------------------- void -modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint32_t timingRegisterValue) +modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint32_t timingRegisterValue, uint8_t isrPriority) { Rcc::enable(); @@ -557,19 +557,19 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint32_t timingRegist %% if shared_irq // Enable Interrupt - NVIC_SetPriority({{ shared_irq }}_IRQn, 10); + NVIC_SetPriority({{ shared_irq }}_IRQn, isrPriority); NVIC_EnableIRQ({{ shared_irq }}_IRQn); %% elif single_interrupt // Enable Interrupt - NVIC_SetPriority(I2C{{ id }}_IRQn, 10); + NVIC_SetPriority(I2C{{ id }}_IRQn, isrPriority); NVIC_EnableIRQ(I2C{{ id }}_IRQn); %% else // Enable Error Interrupt - NVIC_SetPriority(I2C{{ id }}_ER_IRQn, 10); + NVIC_SetPriority(I2C{{ id }}_ER_IRQn, isrPriority); NVIC_EnableIRQ(I2C{{ id }}_ER_IRQn); // Enable Event Interrupt - NVIC_SetPriority(I2C{{ id }}_EV_IRQn, 10); + NVIC_SetPriority(I2C{{ id }}_EV_IRQn, isrPriority); NVIC_EnableIRQ(I2C{{ id }}_EV_IRQn); %% endif diff --git a/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in index 1b4b439747..2239ba5151 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in @@ -99,12 +99,12 @@ public: */ template static void - initialize() + initialize(uint8_t isrPriority = 10u) { constexpr std::optional timingRegisterValue = calculateTimings(); static_assert(bool(timingRegisterValue), "Could not find a valid clock configuration for the requested baudrate"); - initializeWithPrescaler(timingRegisterValue.value()); + initializeWithPrescaler(timingRegisterValue.value(), isrPriority); } static bool @@ -118,7 +118,7 @@ public: private: static void - initializeWithPrescaler(uint32_t timingRegisterValue); + initializeWithPrescaler(uint32_t timingRegisterValue, uint8_t isrPriority = 10u); %% if shared_irq friend void ::{{ shared_irq }}_IRQHandler(); From 9d4baa983399613cafaccafe051e702663115608 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Tue, 12 Mar 2024 13:53:59 +0100 Subject: [PATCH 148/159] [stm32] Fix flash page size for large F103, F105 and F107 devices --- src/modm/platform/flash/stm32/module.lb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modm/platform/flash/stm32/module.lb b/src/modm/platform/flash/stm32/module.lb index cf2dfa6a3b..8cc3db884e 100644 --- a/src/modm/platform/flash/stm32/module.lb +++ b/src/modm/platform/flash/stm32/module.lb @@ -37,7 +37,10 @@ def build(env): ftype = "sector" busy_bit = "FLASH_SR_BSY" elif target.family in ["f1"]: - block_shift = 10 + if target.name in ["05", "07"] or int(flash["size"]) >= 262144: + block_shift = 11 + else: + block_shift = 10 ftype = "page" busy_bit = "FLASH_SR_BSY" elif target.family in ["g0"]: From e1d8a1746a2075e87d4472ff98365f8b58bd79e1 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Thu, 14 Mar 2024 11:56:49 +0100 Subject: [PATCH 149/159] [stm32/sam_x7x] Allow I2C ISR priority to be set --- src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in | 6 ++---- src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in | 6 +++--- src/modm/platform/i2c/stm32/i2c_master.cpp.in | 6 +++--- src/modm/platform/i2c/stm32/i2c_master.hpp.in | 6 +++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in index ef36040818..fcec513030 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in @@ -289,7 +289,7 @@ MODM_ISR(TWIHS{{ id }}) // ---------------------------------------------------------------------------- void -modm::platform::I2cMaster{{ id }}::initializeWithClockConfig(uint32_t cwgrRegister) +modm::platform::I2cMaster{{ id }}::initializeWithClockConfig(uint32_t cwgrRegister, uint8_t isrPriority) { ClockGen::enable(); @@ -307,9 +307,7 @@ modm::platform::I2cMaster{{ id }}::initializeWithClockConfig(uint32_t cwgrRegist // Enable arbitration lost interrupt TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_ARBLST; - // TODO: make priority configurable? - // 10 is also used in the STM32 extended driver - NVIC_SetPriority(TWIHS{{ id }}_IRQn, 10); + NVIC_SetPriority(TWIHS{{ id }}_IRQn, isrPriority); NVIC_EnableIRQ(TWIHS{{ id }}_IRQn); } diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in index 1b881334de..5fd43208b0 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in @@ -146,14 +146,14 @@ public: */ template static void - initialize() + initialize(uint8_t isrPriority = 10u) { static_assert(baudrate <= 400'000, "Baudrate must not exceed 400 kHz for I2C fast mode"); constexpr std::optional registerValue = calculateTimings(); static_assert(bool(registerValue), "Could not find a valid clock configuration for the requested" " baudrate and tolerance"); - initializeWithClockConfig(registerValue.value()); + initializeWithClockConfig(registerValue.value(), isrPriority); } static bool @@ -167,7 +167,7 @@ public: private: static void - initializeWithClockConfig(uint32_t cwgrRegister); + initializeWithClockConfig(uint32_t cwgrRegister, uint8_t isrPriority); }; diff --git a/src/modm/platform/i2c/stm32/i2c_master.cpp.in b/src/modm/platform/i2c/stm32/i2c_master.cpp.in index 36525857d9..75e82b88ed 100644 --- a/src/modm/platform/i2c/stm32/i2c_master.cpp.in +++ b/src/modm/platform/i2c/stm32/i2c_master.cpp.in @@ -588,7 +588,7 @@ MODM_ISR(I2C{{ id }}_ER) // ---------------------------------------------------------------------------- void -modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler) +modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler, uint8_t isrPriority) { // no reset, since we want to keep the transaction attached! @@ -597,9 +597,9 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint8_t peripheralFre I2C{{ id }}->CR1 = I2C_CR1_SWRST; // reset module I2C{{ id }}->CR1 = 0; - NVIC_SetPriority(I2C{{ id }}_ER_IRQn, 10); + NVIC_SetPriority(I2C{{ id }}_ER_IRQn, isrPriority); NVIC_EnableIRQ(I2C{{ id }}_ER_IRQn); - NVIC_SetPriority(I2C{{ id }}_EV_IRQn, 10); + NVIC_SetPriority(I2C{{ id }}_EV_IRQn, isrPriority); NVIC_EnableIRQ(I2C{{ id }}_EV_IRQn); I2C{{ id }}->CR2 = peripheralFrequency; diff --git a/src/modm/platform/i2c/stm32/i2c_master.hpp.in b/src/modm/platform/i2c/stm32/i2c_master.hpp.in index 06459aa04e..3422889b63 100644 --- a/src/modm/platform/i2c/stm32/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32/i2c_master.hpp.in @@ -70,7 +70,7 @@ public: */ template static void - initialize() + initialize(uint8_t isrPriority = 10u) { // calculate the expected clock ratio constexpr uint8_t scalar = (baudrate <= 100'000) ? 2 : ((baudrate <= 300'000) ? 3 : 25); @@ -96,7 +96,7 @@ public: constexpr float trise_raw = max_rise_time < 0 ? 0 : std::floor(max_rise_time / (1'000.f / freq)); constexpr uint8_t trise = trise_raw > 62 ? 63 : (trise_raw + 1); - initializeWithPrescaler(freq, trise, prescaler); + initializeWithPrescaler(freq, trise, prescaler, isrPriority); } static bool @@ -110,7 +110,7 @@ public: private: static void - initializeWithPrescaler(uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler); + initializeWithPrescaler(uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler, uint8_t isrPriority); }; } // namespace platform From a0669259a26dfd5dfb03b8500b965cb3733cf81f Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Thu, 14 Mar 2024 20:13:49 +0100 Subject: [PATCH 150/159] Fix Python syntax Since Python 3.6 backslash-character pairs that are not valid escape sequences are deprecated and generate a 'SyntaxWarning: invalid escape sequence' warning. This is fixed by using raw string. --- src/modm/platform/dma/stm32/module.lb | 4 ++-- src/modm/platform/i2c/stm32-extended/module.lb | 2 +- tools/bitmap/pbm2c.py | 2 +- tools/font_creator/font_export.py | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modm/platform/dma/stm32/module.lb b/src/modm/platform/dma/stm32/module.lb index 0c13840264..3f4cc4344b 100644 --- a/src/modm/platform/dma/stm32/module.lb +++ b/src/modm/platform/dma/stm32/module.lb @@ -43,8 +43,8 @@ def get_irq_list(device): irqs = {v["position"]: v["name"] for v in device.get_driver("core")["vector"]} irqs = [v for v in irqs.values() if v.startswith("DMA") and v[3].isdigit() and not "DMA2D" in v] - instance_pattern = re.compile("(DMA\d_(Ch|Channel|Stream)\d(_\d)*)") - channel_pattern = re.compile("DMA(?P\d)_(Ch|Channel|Stream)(?P(\d(_\d)*))") + instance_pattern = re.compile(r"(DMA\d_(Ch|Channel|Stream)\d(_\d)*)") + channel_pattern = re.compile(r"DMA(?P\d)_(Ch|Channel|Stream)(?P(\d(_\d)*))") irq_list = [] for irq in irqs: instances = [] diff --git a/src/modm/platform/i2c/stm32-extended/module.lb b/src/modm/platform/i2c/stm32-extended/module.lb index 970bc19be7..5c2a6124a8 100644 --- a/src/modm/platform/i2c/stm32-extended/module.lb +++ b/src/modm/platform/i2c/stm32-extended/module.lb @@ -17,7 +17,7 @@ import re global_properties = {} def get_shared_irqs(device): - irq_re = re.compile("I2C\d(_\d)+") + irq_re = re.compile(r"I2C\d(_\d)+") shared_irqs = [v["name"] for v in device.get_driver("core")["vector"]] shared_irqs = [v for v in shared_irqs if irq_re.fullmatch(v)] shared_irq_map = {} diff --git a/tools/bitmap/pbm2c.py b/tools/bitmap/pbm2c.py index 7c745c8d44..94e0477d6b 100644 --- a/tools/bitmap/pbm2c.py +++ b/tools/bitmap/pbm2c.py @@ -34,7 +34,7 @@ # switch to the next line input = input[input.find("\n") + 1:] - result = re.match("^(\d+) (\d+)\n", input) + result = re.match(r"^(\d+) (\d+)\n", input) if not result: print("bad format!") diff --git a/tools/font_creator/font_export.py b/tools/font_creator/font_export.py index abd6c558cb..8f6718f56c 100755 --- a/tools/font_creator/font_export.py +++ b/tools/font_creator/font_export.py @@ -135,7 +135,7 @@ def read_font_file(filename): lines = open(filename).readlines() for line_number, line in enumerate(lines): if char_mode: - result = re.match("^\[([ #]+)\]\n", line) + result = re.match(r"^\[([ #]+)\]\n", line) if not result: raise ParseException("Illegal Format in: %s" % line[:-1], line_number) @@ -165,7 +165,7 @@ def read_font_file(filename): if char_line_count == 0: char_mode = False elif line[0] == '#': - result = re.match("^#(\w+)[ \t]+:[ \t]+(.*)\n", line) + result = re.match(r"^#(\w+)[ \t]+:[ \t]+(.*)\n", line) if not result: print("Error in: ", line) exit(1) @@ -184,7 +184,7 @@ def read_font_file(filename): elif key == "vspace": font.vspace = int(value) elif key == "char": - charMatch = re.match("^(\d+)([ \t]*.*)", value) + charMatch = re.match(r"^(\d+)([ \t]*.*)", value) if not charMatch: raise ParseException("Illegal Format in: %s" % line[:-1], line_number) number = int(charMatch.group(1)) From c43be03e82b22f5980e50415a855bee10ee70c59 Mon Sep 17 00:00:00 2001 From: Raphael Lehmann Date: Thu, 14 Mar 2024 20:15:17 +0100 Subject: [PATCH 151/159] [ext] Update modm-devices submodule --- ext/modm-devices | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/modm-devices b/ext/modm-devices index 3c437bbfa7..cd25d12721 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit 3c437bbfa740d61d99a66de8a36d64281806fd54 +Subproject commit cd25d127216a27390a86eaddbc002a454b3f6997 From cd5d3bfd4871ceb116d05cadf62f7f87be3b0eee Mon Sep 17 00:00:00 2001 From: MatthewMArnold <43590293+MatthewMArnold@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:25:46 -0700 Subject: [PATCH 152/159] [menu] Add optional allocator for ui menu classes (#5) * [menu] Add optional allocator for ui menu classes, used for destroying AbstractViews * [menu] remove coupling between modm gui and modm menu view and menu This is because the gui stuff requires colored menus but the menu code should not have to have colored menus. This wouldn't be an issue if we could dynamic cast but on certain devices you can't do this. --- src/modm/ui/display/color_graphic_display.hpp | 2 - src/modm/ui/gui/view.cpp | 11 +- src/modm/ui/gui/view.hpp | 52 +++++++- src/modm/ui/gui/view_stack.cpp | 2 +- src/modm/ui/gui/view_stack.hpp | 24 +++- src/modm/ui/menu/abstract_menu.cpp | 21 --- src/modm/ui/menu/abstract_menu.hpp | 9 +- src/modm/ui/menu/abstract_view.cpp | 68 ---------- src/modm/ui/menu/abstract_view.hpp | 90 +++---------- src/modm/ui/menu/choice_menu.hpp | 10 +- .../{choice_menu.cpp => choice_menu_impl.hpp} | 50 ++++---- src/modm/ui/menu/communicating_view.hpp | 10 +- src/modm/ui/menu/communicating_view_stack.hpp | 8 +- src/modm/ui/menu/iabstract_view.hpp | 121 ++++++++++++++++++ src/modm/ui/menu/menu_entry_callback.hpp | 8 +- src/modm/ui/menu/module.lb | 3 +- src/modm/ui/menu/scrollable_text.cpp | 3 +- src/modm/ui/menu/standard_menu.hpp | 25 ++-- ...andard_menu.cpp => standard_menu_impl.hpp} | 62 ++++----- src/modm/ui/menu/view_stack.cpp | 79 ------------ src/modm/ui/menu/view_stack.hpp | 71 ++++++++-- 21 files changed, 392 insertions(+), 337 deletions(-) delete mode 100644 src/modm/ui/menu/abstract_menu.cpp delete mode 100644 src/modm/ui/menu/abstract_view.cpp rename src/modm/ui/menu/{choice_menu.cpp => choice_menu_impl.hpp} (72%) create mode 100644 src/modm/ui/menu/iabstract_view.hpp rename src/modm/ui/menu/{standard_menu.cpp => standard_menu_impl.hpp} (61%) delete mode 100644 src/modm/ui/menu/view_stack.cpp diff --git a/src/modm/ui/display/color_graphic_display.hpp b/src/modm/ui/display/color_graphic_display.hpp index 5e9d1e4d2d..2647f1b01b 100644 --- a/src/modm/ui/display/color_graphic_display.hpp +++ b/src/modm/ui/display/color_graphic_display.hpp @@ -5,8 +5,6 @@ #include "graphic_display.hpp" -using namespace modm::platform; - namespace modm { diff --git a/src/modm/ui/gui/view.cpp b/src/modm/ui/gui/view.cpp index 75c5389d8c..1fd4c505f1 100644 --- a/src/modm/ui/gui/view.cpp +++ b/src/modm/ui/gui/view.cpp @@ -18,9 +18,10 @@ // ---------------------------------------------------------------------------- modm::gui::View::View(modm::gui::GuiViewStack* stack, uint8_t identifier, modm::gui::Dimension dimension) : - AbstractView(stack, identifier), stack(stack), - dimension(dimension) + dimension(dimension), + identifier(identifier), + alive(true) { this->display().clear(); } @@ -161,3 +162,9 @@ void modm::gui::View::markDrawn() (*iter)->markDrawn(); } } + +modm::ColorGraphicDisplay& +modm::gui::View::display() +{ + return stack->getDisplay(); +} diff --git a/src/modm/ui/gui/view.hpp b/src/modm/ui/gui/view.hpp index 69d02fcca3..91a41f112a 100644 --- a/src/modm/ui/gui/view.hpp +++ b/src/modm/ui/gui/view.hpp @@ -41,7 +41,7 @@ class GuiViewStack; * @ingroup modm_ui_gui * @author Thorsten Lajewski */ -class View : public modm::AbstractView +class View { friend class GuiViewStack; @@ -61,6 +61,14 @@ class View : public modm::AbstractView virtual void update(); + /** + * @brief hasChanged indicates the current displayed view has changed. + * This function prevents unnecessary drawing of the display + * @return if true the display has to be redrawn. + */ + virtual bool + hasChanged() = 0; + virtual void preUpdate() { @@ -75,14 +83,43 @@ class View : public modm::AbstractView virtual void draw(); + /** + * @brief shortButtonPress handle the action for the pressed button + */ + virtual void + shortButtonPress(modm::MenuButtons::Button /*button*/) + { + // nothing to be done + } + /// Add widget to view bool pack(Widget *w, const modm::glcd::Point &coord); + /** + * @brief isAlive tells the ViewStack if it should remove this screen. + * @return + */ + bool + isAlive() const + { + return this->alive; + } + /// Remove the view from the screen. The viewStack handles the deletion. void remove(); + /** + * @brief onRemove will be called right before the view gets deleted, + * can be reimplemented to reset external data. + */ + virtual void + onRemove() + { + // nothing to be done here + } + /// Set color palette for every contained widget void setColorPalette(ColorPalette& cp); @@ -105,12 +142,25 @@ class View : public modm::AbstractView return stack; } + modm::ColorGraphicDisplay& + display(); + + /** + * @brief getIdentifier of the view. + */ + inline uint8_t getIdentifier(){ + return this->identifier; + } + protected: modm::gui::GuiViewStack* stack; Dimension dimension; WidgetContainer widgets; modm::gui::ColorPalette colorpalette; + + const uint8_t identifier; + bool alive; }; } // namespace gui diff --git a/src/modm/ui/gui/view_stack.cpp b/src/modm/ui/gui/view_stack.cpp index de116a93fc..a5e1b614d3 100644 --- a/src/modm/ui/gui/view_stack.cpp +++ b/src/modm/ui/gui/view_stack.cpp @@ -20,7 +20,7 @@ // ---------------------------------------------------------------------------- modm::gui::GuiViewStack::GuiViewStack(modm::ColorGraphicDisplay* display, modm::gui::inputQueue* queue) : - ViewStack(display), + display(display), input_queue(queue) { } diff --git a/src/modm/ui/gui/view_stack.hpp b/src/modm/ui/gui/view_stack.hpp index 308ce0085d..18c90e9c07 100644 --- a/src/modm/ui/gui/view_stack.hpp +++ b/src/modm/ui/gui/view_stack.hpp @@ -41,7 +41,7 @@ namespace gui * @ingroup modm_ui_gui * @author Thorsten Lajewski */ -class GuiViewStack : public modm::ViewStack +class GuiViewStack { public: GuiViewStack(modm::ColorGraphicDisplay* display, modm::gui::inputQueue* queue); @@ -84,7 +84,29 @@ class GuiViewStack : public modm::ViewStack virtual void update(); + /** + * @brief shortButtonPress pass the button press to the current top view + * @param button the pressed button + */ + + void + shortButtonPress(modm::MenuButtons::Button button) + { + modm::gui::View* top = this->get(); + top->shortButtonPress(button); + } + + /** + * @brief getDisplay access underlying GraphicDisplay + */ + inline modm::ColorGraphicDisplay& + getDisplay() + { + return *this->display; + } + private: + modm::ColorGraphicDisplay *display; modm::Stack< modm::gui::View* , modm::LinkedList< modm::gui::View* > > stack; modm::gui::inputQueue *input_queue; }; diff --git a/src/modm/ui/menu/abstract_menu.cpp b/src/modm/ui/menu/abstract_menu.cpp deleted file mode 100644 index 146cf54108..0000000000 --- a/src/modm/ui/menu/abstract_menu.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2009, Martin Rosekeit - * Copyright (c) 2009-2011, Fabian Greif - * Copyright (c) 2012, Niklas Hauser - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013, Thorsten Lajewski - * - * 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 "abstract_menu.hpp" - -modm::AbstractMenu::AbstractMenu(modm::ViewStack* stack, uint8_t identifier): - modm::AbstractView(stack, identifier) -{ -} diff --git a/src/modm/ui/menu/abstract_menu.hpp b/src/modm/ui/menu/abstract_menu.hpp index 346744e3e8..78cafc0bac 100644 --- a/src/modm/ui/menu/abstract_menu.hpp +++ b/src/modm/ui/menu/abstract_menu.hpp @@ -30,12 +30,17 @@ namespace modm{ * \author Thorsten Lajewski * \ingroup modm_ui_menu */ - class AbstractMenu: public AbstractView + template > + class AbstractMenu : public AbstractView { public: - AbstractMenu(modm::ViewStack* stack, uint8_t identifier); + AbstractMenu(modm::ViewStack* stack, uint8_t identifier) : + modm::AbstractView(stack, identifier) + { + } + virtual ~AbstractMenu() {} virtual void shortButtonPress(modm::MenuButtons::Button button) = 0; diff --git a/src/modm/ui/menu/abstract_view.cpp b/src/modm/ui/menu/abstract_view.cpp deleted file mode 100644 index 96b5ba6e05..0000000000 --- a/src/modm/ui/menu/abstract_view.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009, Martin Rosekeit - * Copyright (c) 2009-2011, Fabian Greif - * Copyright (c) 2010-2011, 2013, Georgi Grinshpun - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013, Thorsten Lajewski - * - * 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 "view_stack.hpp" -#include "abstract_view.hpp" - -// ---------------------------------------------------------------------------- -modm::AbstractView::AbstractView(modm::ViewStack* stack, uint8_t identifier) : - stack(stack), identifier(identifier), alive(true) -{ -} - -modm::AbstractView::~AbstractView() -{ -} - -// ---------------------------------------------------------------------------- -void -modm::AbstractView::update() -{ - //nothing to be done -} - -// ---------------------------------------------------------------------------- -void -modm::AbstractView::shortButtonPress(modm::MenuButtons::Button /*button*/) -{ - //nothing to be done -} - -// ---------------------------------------------------------------------------- -bool -modm::AbstractView::isAlive() const -{ - return this->alive; -} - -void -modm::AbstractView::remove() -{ - this->alive = false; -} - -void -modm::AbstractView::onRemove() -{ - //nothing to be done here -} - -// ---------------------------------------------------------------------------- - -modm::ColorGraphicDisplay& -modm::AbstractView::display() -{ - return stack->getDisplay(); -} diff --git a/src/modm/ui/menu/abstract_view.hpp b/src/modm/ui/menu/abstract_view.hpp index 42a141d5ec..e01eae6f30 100644 --- a/src/modm/ui/menu/abstract_view.hpp +++ b/src/modm/ui/menu/abstract_view.hpp @@ -5,6 +5,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -17,13 +18,14 @@ #ifndef MODM_ABSTRACT_VIEW_HPP #define MODM_ABSTRACT_VIEW_HPP -#include +#include -#include "menu_buttons.hpp" +#include "iabstract_view.hpp" namespace modm { // forward declaration + template class ViewStack; /** @@ -34,8 +36,10 @@ namespace modm *\ingroup modm_ui_menu */ - class AbstractView + template > + class AbstractView : public IAbstractView { + template friend class ViewStack; public: @@ -44,83 +48,27 @@ namespace modm * @param identifier can be used to determine which screen is the currently * displayed on the graphicDisplay */ - AbstractView(modm::ViewStack* stack, uint8_t identifier); - - virtual ~AbstractView() = 0; - - /** - * @brief update The update function of the top most display gets called - * as often as possible. Only the update of the top view in each - * ViewStack gets called. - */ - virtual void - update(); - - /** - * @brief hasChanged indicates the current displayed view has changed. - * This function prevents unnecessary drawing of the display - * @return if true the display has to be redrawn. - */ - virtual bool - hasChanged() = 0; - - /** - * @brief draw determine the output on the Graphic Display - */ - virtual void - draw() = 0; - - - /** - * @brief shortButtonPress handle the action for the pressed button - */ - virtual void - shortButtonPress(modm::MenuButtons::Button button); - - /** - * @brief isAlive tells the ViewStack if it should remove this screen. - * @return - */ - bool - isAlive() const; - - /** - * @brief remove the view from the screen. The viewStack handles the deletion. - */ - void - remove(); - - /** - * @brief getIdentifier of the view. - */ - inline uint8_t getIdentifier(){ - return this->identifier; + AbstractView(modm::ViewStack* stack, uint8_t identifier) : + IAbstractView(identifier), stack(stack) + { } - public: - - modm::ColorGraphicDisplay& - display(); + virtual ~AbstractView() = default; - /** - * @brief onRemove will be called right before the view gets deleted, - * can be reimplemented to reset external data. - */ - virtual void - onRemove(); - - inline modm::ViewStack* + inline modm::ViewStack* getViewStack() { return stack; } - private: - modm::ViewStack* stack; + modm::GraphicDisplay& + display() + { + return stack->getDisplay(); + } - public: - const uint8_t identifier; - bool alive; + private: + modm::ViewStack* stack; }; } diff --git a/src/modm/ui/menu/choice_menu.hpp b/src/modm/ui/menu/choice_menu.hpp index b1ab6e73f5..a4882748c4 100644 --- a/src/modm/ui/menu/choice_menu.hpp +++ b/src/modm/ui/menu/choice_menu.hpp @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2015, Niklas Hauser + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -37,13 +38,14 @@ namespace modm{ * \ingroup modm_ui_menu * */ - class ChoiceMenu: public AbstractMenu + template > + class ChoiceMenu : public AbstractMenu { public: - ChoiceMenu(modm::ViewStack* stack, uint8_t identifier); + ChoiceMenu(modm::ViewStack* stack, uint8_t identifier); - ChoiceMenu(modm::ViewStack* stack, uint8_t identifier, const char* title); + ChoiceMenu(modm::ViewStack* stack, uint8_t identifier, const char* title); /** * @brief addEntry a new entry to the ChoiceMenu @@ -112,4 +114,6 @@ namespace modm{ }; } +#include "choice_menu_impl.hpp" + #endif /* CHOICE_MENU_HPP*/ diff --git a/src/modm/ui/menu/choice_menu.cpp b/src/modm/ui/menu/choice_menu_impl.hpp similarity index 72% rename from src/modm/ui/menu/choice_menu.cpp rename to src/modm/ui/menu/choice_menu_impl.hpp index d8cb4a192c..31e886d604 100644 --- a/src/modm/ui/menu/choice_menu.cpp +++ b/src/modm/ui/menu/choice_menu_impl.hpp @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2015, Niklas Hauser + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -11,11 +12,15 @@ */ // ---------------------------------------------------------------------------- -#include "choice_menu.hpp" +#ifndef CHOICE_MENU_HPP +# error "Don't include this file directly, use choice_menu.hpp instead!" +#endif +#include "abstract_view.hpp" -modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier) : - modm::AbstractMenu(stack, identifier), +template +modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier) : + modm::AbstractMenu(stack, identifier), display_update_time(500), timer(display_update_time), buttonAction(false), @@ -23,11 +28,12 @@ modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier) : homePosition(0), position(0) { - this->maximalDrawnEntrys = (getViewStack()->getDisplay().getHeight()- 16) / 8 ; + this->maximalDrawnEntrys = (this->getViewStack()->getDisplay().getHeight()- 16) / 8 ; } -modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier, const char* title) : - modm::AbstractMenu(stack, identifier), +template +modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier, const char* title) : + modm::AbstractMenu(stack, identifier), display_update_time(500), timer(display_update_time), buttonAction(false), @@ -35,19 +41,19 @@ modm::ChoiceMenu::ChoiceMenu(modm::ViewStack* stack, uint8_t identifier, const c homePosition(0), position(0) { - this->maximalDrawnEntrys = (getViewStack()->getDisplay().getHeight()- 16) / 8 ; + this->maximalDrawnEntrys = (this->getViewStack()->getDisplay().getHeight()- 16) / 8 ; } -void -modm::ChoiceMenu::addEntry(const char* text, bool *valuePtr, bool defaultValue) +template void +modm::ChoiceMenu::addEntry(const char* text, bool *valuePtr, bool defaultValue) { - static uint16_t availableSpace = (getViewStack()->getDisplay().getWidth()-16)/6-6; + static uint16_t availableSpace = (this->getViewStack()->getDisplay().getWidth()-16)/6-6; modm::ChoiceMenuEntry entry(text, availableSpace, valuePtr, defaultValue); this->entries.append(entry); } -void -modm::ChoiceMenu::initialise() +template void +modm::ChoiceMenu::initialise() { EntryList::iterator iter = this->entries.begin(); for(; iter!= this->entries.end(); ++iter){ @@ -62,17 +68,16 @@ modm::ChoiceMenu::initialise() } } -void -modm::ChoiceMenu::setTitle(const char* text) +template void +modm::ChoiceMenu::setTitle(const char* text) { this->title = text; } - -void -modm::ChoiceMenu::draw() +template void +modm::ChoiceMenu::draw() { - modm::ColorGraphicDisplay* display = &getViewStack()->getDisplay(); + modm::GraphicDisplay* display = &this->getViewStack()->getDisplay(); display->clear(); display->setCursor(0,2); (*display) << this->title; @@ -115,8 +120,8 @@ modm::ChoiceMenu::draw() // TODO wenn möglich pfeil nach oben und nach unten einfügen } -bool -modm::ChoiceMenu::hasChanged() +template bool +modm::ChoiceMenu::hasChanged() { if (timer.execute() || this->buttonAction) { @@ -130,9 +135,8 @@ modm::ChoiceMenu::hasChanged() } } - -void -modm::ChoiceMenu::shortButtonPress(modm::MenuButtons::Button button) +template void +modm::ChoiceMenu::shortButtonPress(modm::MenuButtons::Button button) { switch(button) { diff --git a/src/modm/ui/menu/communicating_view.hpp b/src/modm/ui/menu/communicating_view.hpp index 4201321b7f..1a06a194f5 100644 --- a/src/modm/ui/menu/communicating_view.hpp +++ b/src/modm/ui/menu/communicating_view.hpp @@ -5,6 +5,7 @@ * Copyright (c) 2012-2013, 2017-2018, Niklas Hauser * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -24,18 +25,19 @@ namespace modm { /// @ingroup modm_ui_menu + template > class CommunicatingView : public xpcc::Communicatable { public: - CommunicatingView(modm::CommunicatingViewStack* /*stack*/) + CommunicatingView(modm::CommunicatingViewStack* /*stack*/) { } protected: - inline modm::CommunicatingViewStack* - getCommunicatingViewStack(modm::ViewStack* viewStack) + inline modm::CommunicatingViewStack* + getCommunicatingViewStack(modm::ViewStack* viewStack) { - return static_cast(viewStack); + return static_cast*>(viewStack); } }; } diff --git a/src/modm/ui/menu/communicating_view_stack.hpp b/src/modm/ui/menu/communicating_view_stack.hpp index 9f1c5cf7ed..f8d96fa1ef 100644 --- a/src/modm/ui/menu/communicating_view_stack.hpp +++ b/src/modm/ui/menu/communicating_view_stack.hpp @@ -6,6 +6,7 @@ * Copyright (c) 2012-2013, 2017-2018, Niklas Hauser * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -26,11 +27,12 @@ namespace modm { /// @ingroup modm_ui_menu - class CommunicatingViewStack : public ViewStack + template > + class CommunicatingViewStack : public ViewStack { public: - CommunicatingViewStack(modm::ColorGraphicDisplay* display, xpcc::Communicator* communicator) : - ViewStack(display), + CommunicatingViewStack(modm::GraphicDisplay* display, xpcc::Communicator* communicator) : + ViewStack(display), communicator(communicator) { } diff --git a/src/modm/ui/menu/iabstract_view.hpp b/src/modm/ui/menu/iabstract_view.hpp new file mode 100644 index 0000000000..dccaf210ca --- /dev/null +++ b/src/modm/ui/menu/iabstract_view.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020, Matthew Arnold + * + * 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_IABSTRACT_VIEW_HPP +#define MODM_IABSTRACT_VIEW_HPP + +#include + +#include "menu_buttons.hpp" + +namespace modm +{ + // forward declaration + template + class ViewStack; + + class IAbstractView + { + public: + template + friend class ViewStack; + + /** + * @param identifier can be used to determine which screen is the currently + * displayed on the graphicDisplay + */ + IAbstractView(uint8_t identifier) : + identifier(identifier), alive(true) + { + } + + virtual ~IAbstractView() = default; + + /** + * @brief update The update function of the top most display gets called + * as often as possible. Only the update of the top view in each + * ViewStack gets called. + */ + virtual void + update() + { + // nothing to be done + } + + /** + * @brief hasChanged indicates the current displayed view has changed. + * This function prevents unnecessary drawing of the display + * @return if true the display has to be redrawn. + */ + virtual bool + hasChanged() = 0; + + /** + * @brief draw determine the output on the Graphic Display + */ + virtual void + draw() = 0; + + + /** + * @brief shortButtonPress handle the action for the pressed button + */ + virtual void + shortButtonPress(modm::MenuButtons::Button /*button*/) + { + // nothing to be done + } + + /** + * @brief isAlive tells the ViewStack if it should remove this screen. + * @return + */ + bool + isAlive() const + { + return this->alive; + } + + /** + * @brief remove the view from the screen. The viewStack handles the deletion. + */ + void + remove() + { + this->alive = false; + } + + /** + * @brief getIdentifier of the view. + */ + inline uint8_t getIdentifier(){ + return this->identifier; + } + + public: + + /** + * @brief onRemove will be called right before the view gets deleted, + * can be reimplemented to reset external data. + */ + virtual void + onRemove() + { + // nothing to be done here + } + + private: + const uint8_t identifier; + bool alive; + }; +} + +#endif // MODM_IABSTRACT_VIEW_HPP diff --git a/src/modm/ui/menu/menu_entry_callback.hpp b/src/modm/ui/menu/menu_entry_callback.hpp index e5592aa961..23b4d5959a 100644 --- a/src/modm/ui/menu/menu_entry_callback.hpp +++ b/src/modm/ui/menu/menu_entry_callback.hpp @@ -5,6 +5,7 @@ * Copyright (c) 2012, Sascha Schade * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -22,14 +23,15 @@ namespace modm { /// @ingroup modm_ui_menu + template > class MenuEntryCallback { public: - typedef void (modm::AbstractMenu::*Function)(); + typedef void (modm::AbstractMenu::*Function)(); template MenuEntryCallback(M *menu, void (M::*function)()) : - menu(reinterpret_cast(menu)), + menu(reinterpret_cast *>(menu)), function(reinterpret_cast(function)) { } @@ -41,7 +43,7 @@ namespace modm } protected: - modm::AbstractMenu * const menu; + modm::AbstractMenu * const menu; Function const function; }; } diff --git a/src/modm/ui/menu/module.lb b/src/modm/ui/menu/module.lb index f729353585..75c4ec697e 100644 --- a/src/modm/ui/menu/module.lb +++ b/src/modm/ui/menu/module.lb @@ -39,7 +39,8 @@ def prepare(module, options): ":communication:xpcc", ":container", ":processing:timer", - ":ui:display") + ":ui:display", + ":utils") return True def build(env): diff --git a/src/modm/ui/menu/scrollable_text.cpp b/src/modm/ui/menu/scrollable_text.cpp index 72b35221ee..ded7621196 100644 --- a/src/modm/ui/menu/scrollable_text.cpp +++ b/src/modm/ui/menu/scrollable_text.cpp @@ -84,7 +84,7 @@ modm::ScrollableText::getText() if(this->needsScrolling()) { if(!this->isPaused()) - { + { for(uint16_t i = 0; ispace; ++i) { if( (i+this->startPosition) < this->length) @@ -126,5 +126,4 @@ modm::ScrollableText::setToStart() } this->print[space]='\0'; } - } diff --git a/src/modm/ui/menu/standard_menu.hpp b/src/modm/ui/menu/standard_menu.hpp index c7c9e92e40..d92c791f05 100644 --- a/src/modm/ui/menu/standard_menu.hpp +++ b/src/modm/ui/menu/standard_menu.hpp @@ -6,6 +6,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2015, Niklas Hauser + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -34,6 +35,7 @@ namespace modm * \ingroup modm_ui_menu * \author Thorsten Lajewski */ + template > struct MenuEntry { /** @@ -42,10 +44,10 @@ namespace modm * @param space, available to display menu entry in number of letters * @param func callback, which is called when entry is chosen */ - MenuEntry(const char* text, uint16_t space, MenuEntryCallback func); + MenuEntry(const char* text, uint16_t space, MenuEntryCallback func); ScrollableText text; - MenuEntryCallback callback; + MenuEntryCallback callback; }; /** @@ -61,21 +63,22 @@ namespace modm * \author Thorsten Lajewski */ - class StandardMenu : public AbstractMenu + template > + class StandardMenu : public AbstractMenu { public: - StandardMenu(modm::ViewStack* stack, uint8_t identifier); + StandardMenu(modm::ViewStack* stack, uint8_t identifier); - virtual ~StandardMenu() = 0; + virtual ~StandardMenu() {} - StandardMenu(modm::ViewStack* stack, uint8_t identifier, const char* title); + StandardMenu(modm::ViewStack* stack, uint8_t identifier, const char* title); /** * @brief addEntry adds a new option to the displayed list */ void - addEntry(const char* text, MenuEntryCallback func); + addEntry(const char* text, MenuEntryCallback func); /** * @brief setTitle set the title of the menu displayed on top of the list @@ -136,10 +139,14 @@ namespace modm protected: - typedef modm::DoublyLinkedList EntryList; - EntryList entries; + template + using EntryList = modm::DoublyLinkedList >; + + EntryList entries; }; } +#include "standard_menu_impl.hpp" + #endif // MODM_STANDARD_MENU_HPP diff --git a/src/modm/ui/menu/standard_menu.cpp b/src/modm/ui/menu/standard_menu_impl.hpp similarity index 61% rename from src/modm/ui/menu/standard_menu.cpp rename to src/modm/ui/menu/standard_menu_impl.hpp index 892f075a7b..f49bbfe10f 100644 --- a/src/modm/ui/menu/standard_menu.cpp +++ b/src/modm/ui/menu/standard_menu_impl.hpp @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2015, Niklas Hauser + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -11,16 +12,20 @@ */ // ---------------------------------------------------------------------------- -#include "standard_menu.hpp" +#ifndef MODM_STANDARD_MENU_HPP +# error "Don't include this file directly, use standard_menu.hpp instead!" +#endif -modm::MenuEntry::MenuEntry(const char* text, uint16_t space, MenuEntryCallback func): +template +modm::MenuEntry::MenuEntry(const char* text, uint16_t space, MenuEntryCallback func): text(text, space), callback(func) { } -modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier) : - modm::AbstractMenu(stack, identifier), +template +modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier) : + modm::AbstractMenu(stack, identifier), display_update_time(500), timer(std::chrono::milliseconds(display_update_time)), buttonAction(false), @@ -28,11 +33,12 @@ modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier) : homePosition(0), position(0) { - this->maximalDrawnEntrys = (getViewStack()->getDisplay().getHeight()- 16) / 8 ; + this->maximalDrawnEntrys = (this->getViewStack()->getDisplay().getHeight()- 16) / 8 ; } -modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier, const char* title) : - modm::AbstractMenu(stack, identifier), +template +modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier, const char* title) : + modm::AbstractMenu(stack, identifier), display_update_time(500), timer(std::chrono::milliseconds(display_update_time)), buttonAction(false), @@ -40,38 +46,34 @@ modm::StandardMenu::StandardMenu(modm::ViewStack* stack, uint8_t identifier, con homePosition(0), position(0) { - this->maximalDrawnEntrys = (getViewStack()->getDisplay().getHeight()- 16) / 8 ; + this->maximalDrawnEntrys = (this->getViewStack()->getDisplay().getHeight()- 16) / 8 ; } -modm::StandardMenu::~StandardMenu() +template void +modm::StandardMenu::addEntry(const char* text, MenuEntryCallback func) { -} - -void -modm::StandardMenu::addEntry(const char* text, MenuEntryCallback func) -{ - modm::MenuEntry entry(text, (getViewStack()->getDisplay().getWidth()-16)/6, func); + modm::MenuEntry entry(text, (this->getViewStack()->getDisplay().getWidth()-16)/6, func); this->entries.append(entry); } -void -modm::StandardMenu::setTitle(const char* text) +template void +modm::StandardMenu::setTitle(const char* text) { this->title = text; } -void -modm::StandardMenu::draw() +template void +modm::StandardMenu::draw() { - modm::ColorGraphicDisplay* display = &getViewStack()->getDisplay(); + typename modm::GraphicDisplay* display = &this->getViewStack()->getDisplay(); display->clear(); display->setCursor(0,2); (*display) << this->title; display->drawLine(0, 10, display->getWidth(), 10); uint8_t i, count = this->entries.getSize(); - EntryList::iterator iter = this->entries.begin(); + typename EntryList::iterator iter = this->entries.begin(); for(uint8_t j=0; jhomePosition; ++j) { @@ -98,8 +100,8 @@ modm::StandardMenu::draw() // todo add file up and / or down if some entrys are not displayed on screen } -bool -modm::StandardMenu::hasChanged() +template bool +modm::StandardMenu::hasChanged() { if (timer.execute() || this->buttonAction) { @@ -113,14 +115,14 @@ modm::StandardMenu::hasChanged() } } -void -modm::StandardMenu::selectedEntryFunction(uint8_t /*selected*/) +template void +modm::StandardMenu::selectedEntryFunction(uint8_t /*selected*/) { } -void -modm::StandardMenu::shortButtonPress(modm::MenuButtons::Button button) +template void +modm::StandardMenu::shortButtonPress(modm::MenuButtons::Button button) { switch(button) { @@ -128,7 +130,7 @@ modm::StandardMenu::shortButtonPress(modm::MenuButtons::Button button) { if (this->position + 1U < this->entries.getSize()) { - EntryList::iterator iter = this->entries.begin(); + typename EntryList::iterator iter = this->entries.begin(); for (uint8_t j=0; jposition; ++j) { @@ -159,7 +161,7 @@ modm::StandardMenu::shortButtonPress(modm::MenuButtons::Button button) { if (this->position > 0) { - EntryList::iterator iter = this->entries.begin(); + typename EntryList::iterator iter = this->entries.begin(); for (uint8_t j = 0; j < this->position; ++j) { @@ -198,7 +200,7 @@ modm::StandardMenu::shortButtonPress(modm::MenuButtons::Button button) } case modm::MenuButtons::RIGHT: { - EntryList::iterator iter = this->entries.begin(); + typename EntryList::iterator iter = this->entries.begin(); for (uint8_t j = 0; j < this->position; ++j) { diff --git a/src/modm/ui/menu/view_stack.cpp b/src/modm/ui/menu/view_stack.cpp deleted file mode 100644 index f133ecffbe..0000000000 --- a/src/modm/ui/menu/view_stack.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2009, Martin Rosekeit - * Copyright (c) 2009-2011, Fabian Greif - * Copyright (c) 2010-2011, 2013, Georgi Grinshpun - * Copyright (c) 2013, Kevin Läufer - * Copyright (c) 2013, Thorsten Lajewski - * Copyright (c) 2014, Sascha Schade - * - * 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 "view_stack.hpp" - -// ---------------------------------------------------------------------------- -modm::ViewStack::ViewStack(modm::ColorGraphicDisplay* display) : - display(display) -{ -} - -// ---------------------------------------------------------------------------- -modm::ViewStack::~ViewStack() -{ -} - -// ---------------------------------------------------------------------------- -void -modm::ViewStack::pop() -{ - modm::AbstractView *topElement = this->stack.get(); - this->stack.pop(); - - delete topElement; -} - -// ---------------------------------------------------------------------------- -void -modm::ViewStack::update() -{ - modm::AbstractView* top = this->get(); - - if (top == NULL) - return; - - top->update(); - if (top->isAlive()) - { - if (top->hasChanged()) - { - top->draw(); - this->display->update(); - } - } - else - { - // Remove old view - top->onRemove(); - this->pop(); - - // Get new screen - top = this->get(); - top->update(); - this->display->clear(); - top->draw(); - this->display->update(); - } -} - -// ---------------------------------------------------------------------------- -void -modm::ViewStack::shortButtonPress(modm::MenuButtons::Button button) -{ - modm::AbstractView* top = this->get(); - top->shortButtonPress(button); -} diff --git a/src/modm/ui/menu/view_stack.hpp b/src/modm/ui/menu/view_stack.hpp index 188e884af8..c897c99ebe 100644 --- a/src/modm/ui/menu/view_stack.hpp +++ b/src/modm/ui/menu/view_stack.hpp @@ -5,6 +5,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013, Thorsten Lajewski * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2020, Matthew Arnold * * This file is part of the modm project. * @@ -36,19 +37,26 @@ namespace modm * \author Thorsten Lajewski */ + template > class ViewStack { public: - ViewStack(modm::ColorGraphicDisplay* display); + ViewStack(modm::GraphicDisplay* display, const Allocator allocator = Allocator()) : + display(display), + allocator(allocator) + { + } - virtual ~ViewStack(); + virtual ~ViewStack() + { + } /** * @brief get the top view from the stack * @return pointer to view from stack */ - inline modm::AbstractView* + inline modm::AbstractView* get() { return this->stack.get(); @@ -62,11 +70,11 @@ namespace modm * @param view next displayed view */ inline void - push(modm::AbstractView* view) + push(modm::AbstractView* view) { this->stack.push(view); this->getDisplay().clear(); - modm::AbstractView* top = this->get(); + modm::AbstractView* top = this->get(); top->draw(); this->display->update(); } @@ -74,7 +82,7 @@ namespace modm /** * @brief getDisplay access underlying GraphicDisplay */ - inline modm::ColorGraphicDisplay& + inline modm::GraphicDisplay& getDisplay() { return *this->display; @@ -86,10 +94,46 @@ namespace modm */ void - pop(); + pop() + { + modm::AbstractView *topElement = this->stack.get(); + this->stack.pop(); + + allocator.destroy(topElement); + allocator.deallocate(topElement); + } virtual void - update(); + update() + { + modm::AbstractView* top = this->get(); + + if (top == NULL) + return; + + top->update(); + if (top->isAlive()) + { + if (top->hasChanged()) + { + top->draw(); + this->display->update(); + } + } + else + { + // Remove old view + top->onRemove(); + this->pop(); + + // Get new screen + top = this->get(); + top->update(); + this->display->clear(); + top->draw(); + this->display->update(); + } + } /** * @brief shortButtonPress pass the button press to the current top view @@ -97,11 +141,16 @@ namespace modm */ void - shortButtonPress(modm::MenuButtons::Button button); + shortButtonPress(modm::MenuButtons::Button button) + { + modm::AbstractView* top = this->get(); + top->shortButtonPress(button); + } protected: - modm::ColorGraphicDisplay* display; - modm::Stack< modm::AbstractView* , modm::LinkedList< modm::AbstractView* > > stack; + modm::GraphicDisplay* display; + modm::Stack< modm::AbstractView* , modm::LinkedList< modm::AbstractView* > > stack; + Allocator allocator; }; } From 096b38820cfe56ffa1e61dd519b998bc05353448 Mon Sep 17 00:00:00 2001 From: manolipt Date: Mon, 8 May 2023 19:01:20 -0700 Subject: [PATCH 153/159] [utils] Re-implement safe fail behavior into AllocatorBase When pulling from upstream develop, AllocatorBase has been moved to utils/allocator.hpp. This commit simply re-introduces safe fail behavior into AllocatorBase.construct and AllocatorBase.destroy that was previously added by Matthew Arnold (see 3ed0768 for more details). --- src/modm/utils/allocator.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modm/utils/allocator.hpp b/src/modm/utils/allocator.hpp index 880220a3e4..28f600414d 100644 --- a/src/modm/utils/allocator.hpp +++ b/src/modm/utils/allocator.hpp @@ -42,6 +42,7 @@ class AllocatorBase static inline void construct(T* p, const T& value) { + if (p == nullptr) return; // placement new ::new((void *) p) T(value); } @@ -57,6 +58,7 @@ class AllocatorBase static inline void destroy(T* p) { + if (p == nullptr) return; p->~T(); } From b2a0189e6ad573809ef31a09c5c3c75357a8d12d Mon Sep 17 00:00:00 2001 From: MatthewMArnold <43590293+MatthewMArnold@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:25:46 -0700 Subject: [PATCH 154/159] [menu] Add optional allocator for ui menu classes (#5) * [menu] Add optional allocator for ui menu classes, used for destroying AbstractViews * [menu] remove coupling between modm gui and modm menu view and menu This is because the gui stuff requires colored menus but the menu code should not have to have colored menus. This wouldn't be an issue if we could dynamic cast but on certain devices you can't do this. --- test/modm/math/saturation/saturation_test.hpp | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 test/modm/math/saturation/saturation_test.hpp diff --git a/test/modm/math/saturation/saturation_test.hpp b/test/modm/math/saturation/saturation_test.hpp deleted file mode 100644 index 4012c2afea..0000000000 --- a/test/modm/math/saturation/saturation_test.hpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2009, Martin Rosekeit - * Copyright (c) 2009-2010, Fabian Greif - * Copyright (c) 2012, Niklas Hauser - * Copyright (c) 2021, Thomas Sommer - * - * 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 - -/// @ingroup modm_test_test_math -class SaturationTest : public unittest::TestSuite -{ -public: - void - testSigned8bit(); - - void - testUnsigned8bit(); - - void - testSigned16bit(); - - void - testUnsigned16bit(); -}; From 9a534b29bf0dbb60a3b42146e400b9e741b4eea6 Mon Sep 17 00:00:00 2001 From: MatthewMArnold <43590293+MatthewMArnold@users.noreply.github.com> Date: Thu, 9 Sep 2021 22:18:22 -0700 Subject: [PATCH 155/159] [bno055] suppress -Waddress-of-packed-member in bno055.hpp (#7) --- src/modm/driver/inertial/bno055.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modm/driver/inertial/bno055.hpp b/src/modm/driver/inertial/bno055.hpp index 2cf1f4cfca..dc3509ec91 100644 --- a/src/modm/driver/inertial/bno055.hpp +++ b/src/modm/driver/inertial/bno055.hpp @@ -11,6 +11,9 @@ #pragma once +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + #include #include #include @@ -670,3 +673,5 @@ class Bno055 : public bno055, public modm::I2cDevice }; } // namespace modm + +#pragma GCC diagnostic pop From e4b9d41d10c2a959880de657d916ec82b8cbc676 Mon Sep 17 00:00:00 2001 From: MatthewMArnold <43590293+MatthewMArnold@users.noreply.github.com> Date: Thu, 9 Sep 2021 22:32:36 -0700 Subject: [PATCH 156/159] resolve merge conflicts: bno055.hpp c-style cast --- src/modm/driver/inertial/bno055.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/driver/inertial/bno055.hpp b/src/modm/driver/inertial/bno055.hpp index dc3509ec91..a0e61e96dd 100644 --- a/src/modm/driver/inertial/bno055.hpp +++ b/src/modm/driver/inertial/bno055.hpp @@ -660,7 +660,7 @@ class Bno055 : public bno055, public modm::I2cDevice this->transaction.configureWrite(buffer, 2); buffer[2] = RF_CALL( this->runTransaction() ); if (buffer[2]) prev_reg = reg; - RF_RETURN((bool)buffer[2]); + RF_RETURN(static_cast(buffer[2])); } RF_END_RETURN(true); From dac6f820803a4818b91fc7f6be2a3598fb7f3015 Mon Sep 17 00:00:00 2001 From: Kaelin Laundry Date: Mon, 8 Nov 2021 22:21:02 -0800 Subject: [PATCH 157/159] [windows] Silence warning about incomplete assertion support --- src/modm/platform/core/hosted/module.lb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modm/platform/core/hosted/module.lb b/src/modm/platform/core/hosted/module.lb index 0d6122691f..6b07807138 100644 --- a/src/modm/platform/core/hosted/module.lb +++ b/src/modm/platform/core/hosted/module.lb @@ -51,6 +51,6 @@ def build(env): if env.has_module(":architecture:assert"): env.template("../cortex/assert.cpp.in", "assert.cpp") env.template("../cortex/assert_impl.hpp.in", "assert_impl.hpp") - if target.family == "windows": - env.log.error("Assertions are not fully implemented!") + #if target.family == "windows": + # env.log.error("Assertions are not fully implemented!") From ea92c988e8405b3f35cec488e192888118c2936e Mon Sep 17 00:00:00 2001 From: manolipt Date: Tue, 9 May 2023 18:34:35 -0700 Subject: [PATCH 158/159] [menu] Update include path for static allocator in AbstractView --- src/modm/ui/menu/abstract_view.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modm/ui/menu/abstract_view.hpp b/src/modm/ui/menu/abstract_view.hpp index e01eae6f30..4c056d9c23 100644 --- a/src/modm/ui/menu/abstract_view.hpp +++ b/src/modm/ui/menu/abstract_view.hpp @@ -18,7 +18,7 @@ #ifndef MODM_ABSTRACT_VIEW_HPP #define MODM_ABSTRACT_VIEW_HPP -#include +#include #include "iabstract_view.hpp" From 5eb49080792f65aa0b20df1b0b51b31a357e16ba Mon Sep 17 00:00:00 2001 From: snowshoes Date: Sun, 17 Mar 2024 17:44:51 -0700 Subject: [PATCH 159/159] added test hpp file that somehow got missed by rebase --- test/modm/math/saturation/saturation_test.hpp | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/modm/math/saturation/saturation_test.hpp diff --git a/test/modm/math/saturation/saturation_test.hpp b/test/modm/math/saturation/saturation_test.hpp new file mode 100644 index 0000000000..8dd5362190 --- /dev/null +++ b/test/modm/math/saturation/saturation_test.hpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009, Martin Rosekeit + * Copyright (c) 2009-2010, Fabian Greif + * Copyright (c) 2012, Niklas Hauser + * Copyright (c) 2021, Thomas Sommer + * + * 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 + +/// @ingroup modm_test_test_math +class SaturationTest : public unittest::TestSuite +{ +public: + void + testSigned8bit(); + + void + testUnsigned8bit(); + + void + testSigned16bit(); + + void + testUnsigned16bit(); +}; \ No newline at end of file