diff --git a/examples/stm32f072_discovery/tmp102/main.cpp b/examples/stm32f072_discovery/tmp102/main.cpp new file mode 100644 index 0000000000..4902101842 --- /dev/null +++ b/examples/stm32f072_discovery/tmp102/main.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2014-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 + +#include +#include +#include + +#include + +modm::IODeviceWrapper< Usart1, modm::IOBuffer::BlockIfFull > device; +modm::IOStream stream(device); + +typedef I2cMaster1 MyI2cMaster; + + +class ThreadOne : public modm::pt::Protothread +{ +public: + ThreadOne() + : temp(temperatureData, 0x48) + { + } + + bool + update() + { + temp.update(); + + PT_BEGIN(); + + // ping the device until it responds + while(true) + { + // we wait until the task started + if (PT_CALL(temp.ping())) + break; + // otherwise, try again in 100ms + this->timeout.restart(100); + PT_WAIT_UNTIL(this->timeout.isExpired()); + } + + + PT_CALL(temp.setUpdateRate(200)); + PT_CALL(temp.enableExtendedMode()); + + PT_CALL(temp.configureAlertMode( + modm::tmp102::ThermostatMode::Comparator, + modm::tmp102::AlertPolarity::ActiveLow, + modm::tmp102::FaultQueue::Faults6)); + PT_CALL(temp.setLowerLimit(28.f)); + PT_CALL(temp.setUpperLimit(30.f)); + + while (true) + { + { + PT_CALL(temp.readComparatorMode(result)); + float temperature = temperatureData.getTemperature(); + uint8_t tI = (int) temperature; + uint16_t tP = (temperature - tI) * 10000; + stream << "T= " << tI << "."; + if (tP == 0) + { + stream << "0000 C"; + } + else if (tP == 625) + { + stream << "0" << tP << " C"; + } + else + { + stream << tP << " C"; + } + stream << modm::endl; + if (result) stream << "Heat me up!" << modm::endl; + } + this->timeout.restart(200); + PT_WAIT_UNTIL(this->timeout.isExpired()); + Board::LedDown::toggle(); + } + + PT_END(); + } + +private: + bool result; + modm::ShortTimeout timeout; + modm::tmp102::Data temperatureData; + modm::Tmp102 temp; +}; + +ThreadOne one; + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + + Usart1::connect(); + Usart1::initialize(); + + MyI2cMaster::connect(); + MyI2cMaster::initialize(); + + stream << "\n\nRESTART\n\n"; + + while (1) + { + one.update(); + Board::LedUp::toggle(); + } + + return 0; +} diff --git a/examples/stm32f072_discovery/tmp102/project.xml b/examples/stm32f072_discovery/tmp102/project.xml new file mode 100644 index 0000000000..b55910f21c --- /dev/null +++ b/examples/stm32f072_discovery/tmp102/project.xml @@ -0,0 +1,15 @@ + + modm:board:disco-f072rb + + + + + :driver:tmp102 + :io + :platform:gpio + :platform:i2c:1 + :platform:uart:1 + :processing:protothread + :build:scons + + diff --git a/examples/stm32f746g_discovery/tmp102/main.cpp b/examples/stm32f746g_discovery/tmp102/main.cpp new file mode 100644 index 0000000000..a78c0996ed --- /dev/null +++ b/examples/stm32f746g_discovery/tmp102/main.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2014-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 + +#include +#include +#include + +typedef I2cMaster1 MyI2cMaster; + + +class ThreadOne : public modm::pt::Protothread +{ +public: + ThreadOne() + : temp(temperatureData, 0x48) + { + } + + bool + update() + { + temp.update(); + + PT_BEGIN(); + + // ping the device until it responds + while(true) + { + // we wait until the task started + if (PT_CALL(temp.ping())) + break; + // otherwise, try again in 100ms + this->timeout.restart(100); + PT_WAIT_UNTIL(this->timeout.isExpired()); + } + + + PT_CALL(temp.setUpdateRate(200)); + PT_CALL(temp.enableExtendedMode()); + + PT_CALL(temp.configureAlertMode( + modm::tmp102::ThermostatMode::Comparator, + modm::tmp102::AlertPolarity::ActiveLow, + modm::tmp102::FaultQueue::Faults6)); + PT_CALL(temp.setLowerLimit(28.f)); + PT_CALL(temp.setUpperLimit(30.f)); + + while (true) + { + { + PT_CALL(temp.readComparatorMode(result)); + float temperature = temperatureData.getTemperature(); + uint8_t tI = (int) temperature; + uint16_t tP = (temperature - tI) * 10000; + MODM_LOG_INFO << "T= " << tI << "."; + if (tP == 0) + { + MODM_LOG_INFO << "0000 C"; + } + else if (tP == 625) + { + MODM_LOG_INFO << "0" << tP << " C"; + } + else + { + MODM_LOG_INFO << tP << " C"; + } + if (result) { MODM_LOG_INFO << " Heat me up!"; } + MODM_LOG_INFO << modm::endl; + } + this->timeout.restart(200); + PT_WAIT_UNTIL(this->timeout.isExpired()); + Board::LedD13::toggle(); + } + + PT_END(); + } + +private: + bool result; + modm::ShortTimeout timeout; + modm::tmp102::Data temperatureData; + modm::Tmp102 temp; +}; + +ThreadOne one; + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + Board::LedD13::setOutput(modm::Gpio::Low); + + MyI2cMaster::connect(); + MyI2cMaster::initialize(); + + MODM_LOG_INFO << "\n\nRESTART\n\n"; + + while (1) + { + one.update(); + } + + return 0; +} diff --git a/examples/stm32f746g_discovery/tmp102/project.xml b/examples/stm32f746g_discovery/tmp102/project.xml new file mode 100644 index 0000000000..3bdd887bd0 --- /dev/null +++ b/examples/stm32f746g_discovery/tmp102/project.xml @@ -0,0 +1,13 @@ + + modm:board:disco-f746ng + + + + + :driver:tmp102 + :io + :platform:i2c:1 + :processing:protothread + :build:scons + + diff --git a/ext/modm-devices b/ext/modm-devices index c719b9609f..c3e12e3773 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit c719b9609ff7a841bbe142386859cdf231ee0f5a +Subproject commit c3e12e3773c56e0fcca53d96ac5e44f291022216 diff --git a/src/modm/board/disco_f072rb/board.hpp b/src/modm/board/disco_f072rb/board.hpp index 009572e24a..c50d399999 100644 --- a/src/modm/board/disco_f072rb/board.hpp +++ b/src/modm/board/disco_f072rb/board.hpp @@ -32,9 +32,32 @@ namespace Board struct systemClock { static constexpr int Frequency = MHz48; - static constexpr int Usart1 = Frequency; - static constexpr int Can = Frequency; - static constexpr int Spi2 = Frequency; + static constexpr uint32_t Ahb = Frequency; + static constexpr uint32_t Apb = Frequency; + + static constexpr uint32_t Adc = Apb; + static constexpr uint32_t Can = Apb; + + static constexpr uint32_t Spi1 = Apb; + static constexpr uint32_t Spi2 = Apb; + + static constexpr uint32_t Usart1 = Apb; + static constexpr uint32_t Usart2 = Apb; + static constexpr uint32_t Usart3 = Apb; + static constexpr uint32_t Usart4 = Apb; + + static constexpr uint32_t I2c1 = Apb; + static constexpr uint32_t I2c2 = Apb; + + static constexpr uint32_t Timer1 = Apb; + static constexpr uint32_t Timer2 = Apb; + static constexpr uint32_t Timer3 = Apb; + static constexpr uint32_t Timer6 = Apb; + static constexpr uint32_t Timer7 = Apb; + static constexpr uint32_t Timer14 = Apb; + static constexpr uint32_t Timer15 = Apb; + static constexpr uint32_t Timer16 = Apb; + static constexpr uint32_t Timer17 = Apb; static bool inline enable() diff --git a/src/modm/board/disco_f303vc/board.hpp b/src/modm/board/disco_f303vc/board.hpp index ba8e85fbbd..ccd587d877 100644 --- a/src/modm/board/disco_f303vc/board.hpp +++ b/src/modm/board/disco_f303vc/board.hpp @@ -149,9 +149,7 @@ using Int2 = GpioInputE5; // MEMS_INT4 [LSM303DLHC_INT2]: GPXTI5 using Scl = GpioB6; // I2C1_SCL [LSM303DLHC_SCL]: I2C1_SCL using Sda = GpioB7; // I2C1_SDA [LSM303DLHC_SDA]: I2C1_SDA -// Hardware I2C not yet implemented for F3! -//using I2cMaster = I2cMaster1; -using I2cMaster = BitBangI2cMaster; +using I2cMaster = I2cMaster1; using Accelerometer = modm::Lsm303a< I2cMaster >; } @@ -225,7 +223,7 @@ initializeLsm3() lsm3::Drdy::enableExternalInterrupt(); // lsm3::Drdy::enableExternalInterruptVector(12); - lsm3::I2cMaster::connect(); + lsm3::I2cMaster::connect(); lsm3::I2cMaster::initialize(); } diff --git a/src/modm/board/disco_f303vc/module.lb b/src/modm/board/disco_f303vc/module.lb index a21d11d747..cfda22a78c 100644 --- a/src/modm/board/disco_f303vc/module.lb +++ b/src/modm/board/disco_f303vc/module.lb @@ -31,7 +31,7 @@ def prepare(module, options): ":platform:clock", ":platform:core", ":platform:gpio", - ":platform:i2c.bitbang", + ":platform:i2c:1", ":platform:spi:1") return True diff --git a/src/modm/board/disco_f746ng/board.hpp b/src/modm/board/disco_f746ng/board.hpp index 210e15cc50..bd1305a6e4 100644 --- a/src/modm/board/disco_f746ng/board.hpp +++ b/src/modm/board/disco_f746ng/board.hpp @@ -110,6 +110,31 @@ using LedD13 = GpioOutputI1; // User LED 1 (Arduino D13) using Leds = SoftwareGpioPort< LedD13 >; +// Arduino footprint +using A0 = GpioA0; +using A1 = GpioF10; +using A2 = GpioF9; +using A3 = GpioF8; +using A4 = GpioF7; +using A5 = GpioF6; + +using D0 = GpioC7; +using D1 = GpioC6; +using D2 = GpioG6; +using D3 = GpioB4; +using D4 = GpioG7; +using D5 = GpioA8; +using D6 = GpioH6; +using D7 = GpioI3; +using D8 = GpioI2; +using D9 = GpioA15; +using D10 = GpioI0; +using D11 = GpioB15; +using D12 = GpioB14; +using D13 = GpioI1; +using D14 = GpioB9; +using D15 = GpioB8; + namespace stlink { diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index 70e10200e8..6a36299e3d 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -96,7 +96,7 @@ public: SynchronousPrescaler4 = {{ adc_ccr }}_CKMODE_1 | {{ adc_ccr }}_CKMODE_0, }; -%% if target["family"] in ["l4"] +%% if clock_mux // ADCs clock source selection enum class ClockSource : uint32_t { @@ -235,7 +235,7 @@ public: */ static inline void initialize( const ClockMode clk = ClockMode::DoNotChange, -%% if target["family"] in ["l4"] +%% if clock_mux const ClockSource clk_src = ClockSource::SystemClock, %% endif const Prescaler pre = Prescaler::Disabled, diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index 7b693b7b26..048d213065 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -19,7 +19,7 @@ void modm::platform::Adc{{ id }}::initialize(const ClockMode clk, -%% if target["family"] in ["l4"] +%% if clock_mux const ClockSource clk_src, %% endif const Prescaler pre, @@ -27,14 +27,19 @@ modm::platform::Adc{{ id }}::initialize(const ClockMode clk, { uint32_t tmp = 0; -%% if target["family"] in ["f3"] // enable clock +%% if target["family"] in ["f3"] RCC->AHBENR |= RCC_AHBENR_ADC{{ id_common }}EN; %% elif target["family"] in ["l4"] - // enable and select clock RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN; +%% endif + +%% if clock_mux + // select clock source RCC->CCIPR |= static_cast(clk_src); +%% endif +%% if target["family"] in ["l4"] // Disable deep power down ADC{{ id }}->CR &= ~ADC_CR_DEEPPWD; %% endif diff --git a/src/modm/platform/adc/stm32f3/module.lb b/src/modm/platform/adc/stm32f3/module.lb index 4bab364ff8..405183b052 100644 --- a/src/modm/platform/adc/stm32f3/module.lb +++ b/src/modm/platform/adc/stm32f3/module.lb @@ -76,15 +76,19 @@ class Instance(Module): properties["adc_pre"] = "ADCPRE34" properties["id_common"] = "34" properties["id_common_u"] = "3_4_COMMON" - else: + properties["clock_mux"] = False + else: # L4 properties["adc_ccr"] = "ADC_CCR" if len(driver["instance"]) == 1: properties["id_common"] = "1" properties["id_common_u"] = "1_COMMON" + elif len(driver["instance"]) == 2: + properties["id_common"] = "12" + properties["id_common_u"] = "12_COMMON" else: properties["id_common"] = "123" properties["id_common_u"] = "123_COMMON" - + properties["clock_mux"] = (target["name"] not in ("12", "22")) env.substitutions = properties env.outbasepath = "modm/src/modm/platform/adc" diff --git a/src/modm/platform/clock/stm32/clock.cpp.in b/src/modm/platform/clock/stm32/clock.cpp.in index 552b4afb39..32d5af1a72 100644 --- a/src/modm/platform/clock/stm32/clock.cpp.in +++ b/src/modm/platform/clock/stm32/clock.cpp.in @@ -182,7 +182,11 @@ modm::platform::ClockControl::enablePll(PllSource source, uint8_t pllM, // Read reserved values and clear all other values tmp = RCC->PLLCFGR & ~(RCC_PLLCFGR_PLLSRC | RCC_PLLCFGR_PLLM - | RCC_PLLCFGR_PLLN | RCC_PLLCFGR_PLLPEN | RCC_PLLCFGR_PLLP + | RCC_PLLCFGR_PLLN +%% if pll_p + | RCC_PLLCFGR_PLLPEN + | RCC_PLLCFGR_PLLP +%% endif %% if sai2clkdiv is defined | RCC_PLLCFGR_PLLPDIV %% endif diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 3b77aee428..18ad06a2b6 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -53,6 +53,7 @@ def build(env): properties["pllprediv2"] = False # FIXME: not sure what value this should have properties["hsi48"] = \ target["family"] == "f0" and target["name"] in ["42", "48", "71", "72", "78", "91", "98"] + properties["pll_p"] = target["family"] == "l4" and target["name"] not in ["12", "22"] env.substitutions = properties env.outbasepath = "modm/src/modm/platform/clock" diff --git a/src/modm/platform/i2c/stm32l4/i2c_master.cpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in similarity index 88% rename from src/modm/platform/i2c/stm32l4/i2c_master.cpp.in rename to src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in index f99f07a5b2..7fafc3e394 100644 --- a/src/modm/platform/i2c/stm32l4/i2c_master.cpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.cpp.in @@ -52,7 +52,9 @@ #include #include +%% if not shared_interrupt MODM_ISR_DECL(I2C{{ id }}_ER); +%% endif namespace { @@ -257,12 +259,86 @@ namespace callStarting(); } } + + bool modm_always_inline + handleError() + { + uint16_t sr1 = I2C{{ id }}->ISR; + + if (sr1 & I2C_ISR_BERR) + { + DEBUG_STREAM("BUS ERROR"); + I2C{{ id }}->ICR |= I2C_ICR_BERRCF; + error = modm::I2cMaster::Error::BusCondition; + } + else if (sr1 & I2C_ISR_NACKF) + { // acknowledge fail + I2C{{ id }}->ICR |= I2C_ICR_NACKCF; + DEBUG_STREAM("ACK FAIL"); + // may also be ADDRESS_NACK + error = starting.address ? modm::I2cMaster::Error::AddressNack : modm::I2cMaster::Error::DataNack; + } + else if (sr1 & I2C_ISR_ARLO) + { // arbitration lost + I2C{{ id }}->ICR |= I2C_ICR_ARLOCF; + DEBUG_STREAM("ARBITRATION LOST"); + error = modm::I2cMaster::Error::ArbitrationLost; + } + else if ((sr1 & I2C_ISR_TIMEOUT) || (sr1 & I2C_ISR_ALERT) || (sr1 & I2C_ISR_PECERR)) + { + // should only occur in unsupported SMBus mode + DEBUG_STREAM("UNKNOWN, SMBUS"); + I2C{{ id }}->ICR |= I2C_ICR_ALERTCF; + I2C{{ id }}->ICR |= I2C_ICR_TIMOUTCF; + I2C{{ id }}->ICR |= I2C_ICR_PECCF; + error = modm::I2cMaster::Error::Unknown; + } + else if (sr1 & I2C_ISR_OVR) + { + // should not occur in master mode + DEBUG_STREAM("UNKNOWN"); + I2C{{ id }}->ICR |= I2C_ICR_OVRCF; + error = modm::I2cMaster::Error::Unknown; + } + else + { + return false; + } + + if (transaction) transaction->detaching(modm::I2c::DetachCause::ErrorCondition); + transaction = nullptr; + + // Clear flags and interrupts + writing.length = 0; + reading.length = 0; + + DEBUG_STREAM("disable interrupts"); + I2C{{ id }}->CR1 &= ~( + I2C_CR1_STOPIE | + I2C_CR1_TCIE | + I2C_CR1_RXIE | + I2C_CR1_TXIE | + I2C_CR1_RXIE); + + callNextTransaction(); + return true; + } + } // ---------------------------------------------------------------------------- +%% if shared_interrupt +MODM_ISR(I2C{{ id }}) +%% else MODM_ISR(I2C{{ id }}_EV) +%% endif { DEBUG_STREAM("\n=== IRQ ==="); + +%% if shared_interrupt + handleError(); +%% endif + uint16_t isr = I2C{{ id }}->ISR; I2C{{ id }}->CR1 &= ~(I2C_CR1_STOPIE | I2C_CR1_TCIE | I2C_CR1_RXIE | I2C_CR1_TXIE); @@ -407,60 +483,17 @@ MODM_ISR(I2C{{ id }}_EV) } // ---------------------------------------------------------------------------- +%% if not shared_interrupt MODM_ISR(I2C{{ id }}_ER) { - DEBUG_STREAM("ERROR!"); - uint16_t sr1 = I2C{{ id }}->ISR; - - if (sr1 & I2C_ISR_BERR) - { - DEBUG_STREAM("BUS ERROR"); - I2C{{ id }}->ICR |= I2C_ICR_BERRCF; - error = modm::I2cMaster::Error::BusCondition; - } - else if (sr1 & I2C_ISR_NACKF) - { // acknowledge fail - I2C{{ id }}->ICR |= I2C_ICR_NACKCF; - DEBUG_STREAM("ACK FAIL"); - // may also be ADDRESS_NACK - error = starting.address ? modm::I2cMaster::Error::AddressNack : modm::I2cMaster::Error::DataNack; - } - else if (sr1 & I2C_ISR_ARLO) - { // arbitration lost - I2C{{ id }}->ISR |= I2C_ICR_ARLOCF; - DEBUG_STREAM("ARBITRATION LOST"); - error = modm::I2cMaster::Error::ArbitrationLost; - } - else if (error == modm::I2cMaster::Error::NoError) - { - DEBUG_STREAM("UNKNOWN"); - error = modm::I2cMaster::Error::Unknown; - } - - if (transaction) transaction->detaching(modm::I2c::DetachCause::ErrorCondition); - transaction = nullptr; - - // Overrun error is not handled here separately - - // Clear flags and interrupts - writing.length = 0; - reading.length = 0; - - DEBUG_STREAM("disable interrupts"); - I2C{{ id }}->CR1 &= ~( - I2C_CR1_STOPIE | - I2C_CR1_TCIE | - I2C_CR1_RXIE | - I2C_CR1_TXIE | - I2C_CR1_RXIE); - - callNextTransaction(); + handleError(); } +%% endif // ---------------------------------------------------------------------------- void -modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(/* uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler */) +modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(uint32_t timingRegisterValue) { // Enable clock RCC->APB1ENR{{aid}} |= RCC_APB1ENR{{aid}}_I2C{{ id }}EN; @@ -476,16 +509,8 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(/* uint8_t peripheral // 39.4.8: Before enabling the peripheral, the I2C master clock must be configured by setting the // SCLH and SCLL bits in the I2C_TIMINGR register. - // PRESC = 0x02 // 48 MHz / 3 = 16 MHz 62.5 nsec - // SCLDEL = 0x03 // Data setup time ( 3 + 1) * 62.5 nsec = 250 nsec - // SDADEL = 0x00 // Data hold time = ( 0 + 1) * 62.5 nsec = 625 nsec - // SCLH = 0x3e // SCL high period = (62 + 1) * 62.5 nsec = 3.9375 msec - // SCLL = 0x5d // SCL low period = (93 + 1) * 62.5 nsec = 5.8750 msec - // = 9.8125 msec - // FIXME Hard-coded values for 100 kHz at 48 MHz - static constexpr uint32_t timing = 0x20303E5D; static constexpr uint32_t TIMING_CLEAR_MASK = 0xF0FFFFFF; - I2C{{ id }}->TIMINGR = timing & TIMING_CLEAR_MASK; + I2C{{ id }}->TIMINGR = timingRegisterValue & TIMING_CLEAR_MASK; // Disable Own Address1 before set the Own Address1 configuration I2C{{ id }}->OAR1 &= ~I2C_OAR1_OA1EN; @@ -518,6 +543,11 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(/* uint8_t peripheral // Configure Generalcall and NoStretch mode I2C{{ id }}->CR1 = (0 | I2C_CR1_NOSTRETCH); +%% if shared_interrupt + // Enable Interrupt + NVIC_SetPriority(I2C{{ id }}_IRQn, 10); + NVIC_EnableIRQ(I2C{{ id }}_IRQn); +%% else // Enable Error Interrupt NVIC_SetPriority(I2C{{ id }}_ER_IRQn, 10); NVIC_EnableIRQ(I2C{{ id }}_ER_IRQn); @@ -525,6 +555,7 @@ modm::platform::I2cMaster{{ id }}::initializeWithPrescaler(/* uint8_t peripheral // Enable Event Interrupt NVIC_SetPriority(I2C{{ id }}_EV_IRQn, 10); NVIC_EnableIRQ(I2C{{ id }}_EV_IRQn); +%% endif // Enable peripheral I2C{{ id }}->CR1 |= I2C_CR1_PE; diff --git a/src/modm/platform/i2c/stm32l4/i2c_master.hpp.in b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in similarity index 72% rename from src/modm/platform/i2c/stm32l4/i2c_master.hpp.in rename to src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in index 024578d7b3..4702f6e3f4 100644 --- a/src/modm/platform/i2c/stm32l4/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32-extended/i2c_master.hpp.in @@ -18,6 +18,8 @@ #include #include +#include "i2c_timing_calculator.hpp" + namespace modm { @@ -39,6 +41,31 @@ class I2cMaster{{ id }} : public ::modm::I2cMaster public: static constexpr size_t TransactionBufferSize = {{ options["buffer.transaction"] }}; +private: + template + static constexpr std::optional + calculateTimings() + { + constexpr I2cParameters parameters = { + .peripheralClock = SystemClock::I2c{{ id }}, + .targetSpeed = baudrate, + .tolerance = tolerance, + .digitalFilterLength = 0, + .enableAnalogFilter = true, + .riseTime = 0, + .fallTime = 0 + }; + + auto calculator = I2cTimingCalculator{parameters}; + + std::optional timings = calculator.calculateTimings(); + if(timings) { + return I2cTimingCalculator::timingsToRegisterValue(timings.value()); + } else { + return std::nullopt; + } + } + public: template< template class... Signals, ResetDevices reset = ResetDevices::Standard> static void @@ -73,10 +100,10 @@ public: static modm_always_inline void initialize() { - // FIXME: Hard coded to 100 kHz at 48 MHz CPU - static_assert(baudrate == Baudrate::Standard, "FIXME: Hard coded to 100 kHz at 48 MHz CPU."); - static_assert(SystemClock::I2c{{ id }} == modm::clock::MHz48, "FIXME: Hard coded to 100 kHz at 48 MHz CPU."); - initializeWithPrescaler(/* freq, trise, prescaler */); + constexpr std::optional timingRegisterValue = calculateTimings(); + static_assert(bool(timingRegisterValue), "Could not find a valid clock configuration for the requested baudrate"); + + initializeWithPrescaler(timingRegisterValue.value()); } // start documentation inherited @@ -92,9 +119,10 @@ public: private: static void - initializeWithPrescaler(/* uint8_t peripheralFrequency, uint8_t riseTime, uint16_t prescaler */); + initializeWithPrescaler(uint32_t timingRegisterValue); }; + } // namespace platform } // namespace modm diff --git a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp new file mode 100644 index 0000000000..05913eeb89 --- /dev/null +++ b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2018, 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_I2C_TIMING_CALCULATOR_HPP +#define MODM_STM32_I2C_TIMING_CALCULATOR_HPP + +#include +#include +#include +#include + +namespace modm +{ + +namespace platform +{ + +struct I2cMasterTimings +{ + uint8_t prescaler; + uint8_t sdaDel; + uint8_t sclDel; + uint8_t sclLow; + uint8_t sclHigh; +}; + +struct I2cParameters +{ + uint32_t peripheralClock; + uint32_t targetSpeed; + uint16_t tolerance; + uint8_t digitalFilterLength; + bool enableAnalogFilter; + float riseTime; + float fallTime; +}; + +/** + * STM32 extended I2C timing calculator + * + * @author Christopher Durand + * @ingroup modm_platform_i2c + */ +class I2cTimingCalculator +{ +public: + explicit constexpr I2cTimingCalculator(const I2cParameters& parameters) + : params{parameters} {} + + constexpr std::optional + calculateTimings() const + { + uint16_t prescalerMask{findValidPrescalers()}; + if(prescalerMask == 0) { + return std::nullopt; + } + + if((params.fallTime > ModeConstants::tFallMax[modeIndex()]) + || (params.riseTime > ModeConstants::tRiseMax[modeIndex()])) { + return std::nullopt; + } + + bool found = false; + float lastError = 1e10; + uint8_t bestSclLow = 0; + uint8_t bestSclHigh = 0; + uint8_t bestPrescaler = 16; + + for(int prescaler = 15; prescaler >= 0; --prescaler) + { + if(not (prescalerMask & (1 << prescaler))) { + continue; + } + + auto [valid, sclLowMin, sclHighMin] = minimumSclLowHigh(prescaler); + if(!valid) { + continue; + } + + const uint16_t sclSumMax = maximumSclSum(prescaler); + const uint16_t sclLowMax = std::min(sclSumMax, 255); + + for(uint16_t sclLow = sclLowMin; sclLow <= sclLowMax; ++sclLow) { + auto sclHighMax = std::min(sclSumMax - sclLow, 255); + + auto sclHigh = findBestSclHigh(prescaler, sclLow, sclHighMin, sclHighMax); + 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*1000) < params.tolerance) && (error <= lastError) && (prescaler<=bestPrescaler)) { + lastError = error; + bestPrescaler = prescaler; + bestSclLow = sclLow; + bestSclHigh = sclHigh; + found = true; + } + } + } + + if(found) { + I2cMasterTimings timings = {}; + timings.prescaler = bestPrescaler; + + auto [sdaDel, sclDel] = findHoldTimeSettings(bestPrescaler).value(); + timings.sdaDel = sdaDel; + timings.sclDel = sclDel; + + timings.sclLow = bestSclLow; + timings.sclHigh = bestSclHigh; + + return timings; + } + + return std::nullopt; + } + + static constexpr uint32_t + timingsToRegisterValue(const I2cMasterTimings& timings) + { + uint32_t value = timings.sclLow; + value |= timings.sclHigh << 8; + value |= (timings.sdaDel & 0b1111) << 16; + value |= (timings.sclDel & 0b1111) << 20; + value |= (timings.prescaler & 0b1111) << 28; + return value; + } + +private: + I2cParameters params; + + const float FilterDelay = (params.enableAnalogFilter ? AnalogFilterDelayMin : 0) + + float(params.digitalFilterLength) / params.peripheralClock; + + const float SyncTime = FilterDelay + (2.0f / params.peripheralClock); + + struct ModeConstants + { + // index 0: standard 100kHz + // index 1: fast 400kHz + // index 2: fast+ 1000kHz + + static constexpr std::array tFallMax = { + 300.0e-9f, + 300.0e-9f, + 120.0e-9f + }; + + static constexpr std::array tRiseMax = { + 1000.0e-9f, + 300.0e-9f, + 120.0e-9f + }; + + static constexpr std::array tHdDatMax = { + 3450.0e-9f, + 900.0e-9f, + 450.0e-9f + }; + + static constexpr std::array tSuDatMin = { + 250.0e-9f, + 100.0e-9f, + 50.0e-9f + }; + + static constexpr std::array tLowMin = { + 4.7e-6f, + 1.3e-6f, + 0.5e-6f + }; + + static constexpr std::array tHighMin = { + 4.0e-6f, + 0.6e-6f, + 0.26e-6f + }; + }; + + static constexpr auto FastThreshold = 250'000.f; + static constexpr auto FastPlusThreshold = 700'000.f; + + static constexpr auto AnalogFilterDelayMin = 50.0e-9f; + static constexpr auto AnalogFilterDelayMax = 260.0e-9f; + + constexpr uint8_t + modeIndex() const + { + if(params.targetSpeed < FastThreshold) { + return 0; + } else if(params.targetSpeed < FastPlusThreshold) { + return 1; + } else { + return 2; + } + } + + /// Calculate hold time settings for a specific prescaler + constexpr std::optional> + findHoldTimeSettings(uint8_t prescaler) const + { + auto sdaDelMinTime = params.fallTime - AnalogFilterDelayMin + - (float(params.digitalFilterLength + 3) / params.peripheralClock); + + sdaDelMinTime = std::max(0.0f, sdaDelMinTime); + + auto sdaDelMaxTime = ModeConstants::tHdDatMax[modeIndex()]; + sdaDelMaxTime -= params.riseTime; + sdaDelMaxTime -= (float(params.digitalFilterLength + 4) / params.peripheralClock); + sdaDelMaxTime -= AnalogFilterDelayMax; + + sdaDelMaxTime = std::max(0.0f, sdaDelMaxTime); + + auto sclDelMinTime = params.riseTime + ModeConstants::tSuDatMin[modeIndex()]; + + for(uint8_t sdaDel = 0; sdaDel <= 15; ++sdaDel) { + auto sdaDelTime = float(sdaDel) * (prescaler + 1) / params.peripheralClock; + + if(sdaDelTime < sdaDelMinTime || sdaDelTime > sdaDelMaxTime) { + continue; + } + + for(uint8_t sclDel = 0; sclDel <= 15; ++sclDel) { + auto sclDelTime = float(sclDel + 1) * (prescaler + 1) / params.peripheralClock; + + if(sclDelTime >= sclDelMinTime) { + return std::tuple{sdaDel, sclDel}; + } + } + } + + return std::nullopt; + } + + /// Find prescaler values for which valid hold times can be achieved + // \return Bit mask of valid prescaler config values (0 to 15) + constexpr uint16_t + findValidPrescalers() const + { + uint16_t prescalerMask = 0; + + for(uint8_t prescaler = 0; prescaler <= 15; ++prescaler) { + if(findHoldTimeSettings(prescaler)) { + prescalerMask |= (1 << prescaler); + } + } + + return prescalerMask; + } + + constexpr std::tuple + minimumSclLowHigh(uint8_t prescaler) const + { + auto clockPeriod = float(prescaler + 1) / params.peripheralClock; + + float lowMinFloat = std::max( + ((ModeConstants::tLowMin[modeIndex()] - SyncTime) / clockPeriod) - 1, + ((4.0f / params.peripheralClock) + FilterDelay - SyncTime) / clockPeriod - 1 + ); + + float highMinFloat = std::max( + ((ModeConstants::tHighMin[modeIndex()] - SyncTime) / clockPeriod) - 1, + ((1.0f / params.peripheralClock) - SyncTime) / clockPeriod - 1 + ); + + lowMinFloat = std::ceil(lowMinFloat); + highMinFloat = std::ceil(highMinFloat); + + if(lowMinFloat > 255 || highMinFloat > 255) { + return {false, 255, 255}; + } + + uint8_t lowMin = (uint8_t) std::max(lowMinFloat, 0.f); + uint8_t highMin = (uint8_t) std::max(highMinFloat, 0.f); + return {true, lowMin, highMin}; + } + + constexpr uint16_t + maximumSclSum(uint8_t prescaler) const + { + auto clockPeriod = float(prescaler + 1) / params.peripheralClock; + + auto targetSclTime = 1.f / params.targetSpeed; + auto sclTimeMax = targetSclTime * (1.f + params.tolerance / 1000.f); + auto maxSclSum = ((sclTimeMax - params.riseTime - params.fallTime - 2*SyncTime) + / clockPeriod) - 2; + + return (uint16_t) std::max(0.0f, std::min(maxSclSum, 255.0f * 2)); + } + + constexpr uint8_t + findBestSclHigh(uint8_t prescaler, uint8_t sclLow, uint8_t min, uint8_t max) const + { + auto clockPeriod = float(prescaler + 1) / params.peripheralClock; + auto sclLowTime = (sclLow + 1) * clockPeriod + SyncTime; + + auto targetSclTime = 1.f / params.targetSpeed; + auto targetSclHighTime = targetSclTime - sclLowTime + - params.riseTime - params.fallTime; + + auto targetSclHigh = std::round((targetSclHighTime - SyncTime) / clockPeriod - 1); + + return (uint8_t) std::max(min, std::min(targetSclHigh, max)); + } + + constexpr float + calculateSpeed(uint8_t prescaler, uint16_t sclLow, uint16_t sclHigh) const + { + auto clockPeriod = float(prescaler + 1) / params.peripheralClock; + + auto sclLowTime = (sclLow + 1) * clockPeriod + SyncTime; + auto sclHighTime = (sclHigh + 1) * clockPeriod + SyncTime; + + auto sclTime = sclLowTime + sclHighTime + params.riseTime + params.fallTime; + auto speed = 1.0f / sclTime; + + return speed; + } +}; + +} // namespace platform + +} // namespace modm + +#endif // MODM_STM32_I2C_TIMING_CALCULATOR_HPP diff --git a/src/modm/platform/i2c/stm32l4/module.lb b/src/modm/platform/i2c/stm32-extended/module.lb similarity index 81% rename from src/modm/platform/i2c/stm32l4/module.lb rename to src/modm/platform/i2c/stm32-extended/module.lb index d2ba8aefac..e2393cff32 100644 --- a/src/modm/platform/i2c/stm32l4/module.lb +++ b/src/modm/platform/i2c/stm32-extended/module.lb @@ -28,11 +28,6 @@ class Instance(Module): maximum=2 ** 16 - 2, default=8)) - # FIXME STM32F7 have a different I2C peripherial - device = options[":target"] - if device.partname.startswith("stm32f7"): - return False - return True def build(self, env): @@ -42,7 +37,16 @@ class Instance(Module): properties = device.properties properties["target"] = target = device.identifier properties["id"] = self.instance - properties["aid"] = "2" if self.instance == 4 else "1" + + if target["family"] == "l4": + properties["aid"] = "2" if self.instance == 4 else "1" + else: + properties["aid"] = "" + + if target["family"] == "f0" or target["family"] == "l0": + properties["shared_interrupt"] = True + else: + properties["shared_interrupt"] = False env.substitutions = properties env.outbasepath = "modm/src/modm/platform/i2c" @@ -50,7 +54,6 @@ class Instance(Module): env.template("i2c_master.cpp.in", "i2c_master_{}.cpp".format(self.instance)) env.template("i2c_master.hpp.in", "i2c_master_{}.hpp".format(self.instance)) - def init(module): module.name = "i2c" module.parent = "platform" @@ -58,7 +61,7 @@ def init(module): def prepare(module, options): device = options[":target"] - if not device.has_driver("i2c:stm32l4"): + if not device.has_driver("i2c:stm32-extended"): return False module.depends( @@ -77,4 +80,6 @@ def prepare(module, options): return True def build(env): - pass + env.outbasepath = "modm/src/modm/platform/i2c" + env.copy("i2c_timing_calculator.hpp") + diff --git a/src/modm/platform/i2c/stm32/module.lb b/src/modm/platform/i2c/stm32/module.lb index 2a864f7dd7..2b67f03f56 100644 --- a/src/modm/platform/i2c/stm32/module.lb +++ b/src/modm/platform/i2c/stm32/module.lb @@ -29,11 +29,6 @@ class Instance(Module): maximum=2 ** 16 - 2, default=8)) - # FIXME STM32F7 have a different I2C peripherial - device = options[":target"] - if device.partname.startswith("stm32f7"): - return False - return True def build(self, env):