From 339cbc762bc383788b22549b5f09250778f1aa82 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 7 Oct 2018 00:16:05 +0200 Subject: [PATCH 1/5] [unittest] String compare any operator[] type --- src/unittest/harness.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/unittest/harness.hpp b/src/unittest/harness.hpp index fe4c5b2517..e3be795ce4 100644 --- a/src/unittest/harness.hpp +++ b/src/unittest/harness.hpp @@ -117,16 +117,20 @@ namespace unittest } // ------------------------------------------------------------------------ - inline void - printString(modm::IOStream& stream, const char* a, const char* b, size_t pos) + template + void + printString(modm::IOStream& stream, const A& a, const B& b, size_t pos) { - stream << modm::endl << a << modm::endl << b << modm::endl; + stream << modm::endl; + size_t ii=0; while(a[ii]) stream << a[ii++]; stream << modm::endl; + ii=0; while(b[ii]) stream << b[ii++]; stream << modm::endl; for(size_t ii = 0; ii < pos; ++ii) { stream << " "; } stream << "^" << modm::endl; } - inline bool - checkString(const char* a, const char* b, unsigned int line) + template + bool + checkString(const A& a, const B& b, unsigned int line) { size_t ii = 0; while(a[ii] != '\0' && b[ii] != '\0') { From 9d8bbfa3f9457222f7b5258491f4cc9948df465d Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 5 Oct 2018 23:52:08 +0200 Subject: [PATCH 2/5] [driver] Add GpioSampler for IO analysis --- README.md | 15 +- src/modm/driver/gpio/gpio_sampler.cpp.in | 126 +++++++++++++++++ src/modm/driver/gpio/gpio_sampler.hpp.in | 111 +++++++++++++++ src/modm/driver/gpio/gpio_sampler.lb | 58 ++++++++ src/modm/driver/gpio/gpio_sampler_impl.hpp.in | 128 ++++++++++++++++++ src/modm/platform/clock/stm32/rcc.cpp.in | 1 + src/modm/platform/clock/stm32/rcc_impl.hpp.in | 2 + 7 files changed, 434 insertions(+), 7 deletions(-) create mode 100644 src/modm/driver/gpio/gpio_sampler.cpp.in create mode 100644 src/modm/driver/gpio/gpio_sampler.hpp.in create mode 100644 src/modm/driver/gpio/gpio_sampler.lb create mode 100644 src/modm/driver/gpio/gpio_sampler_impl.hpp.in diff --git a/README.md b/README.md index f3282a1547..f69d6ee8ed 100644 --- a/README.md +++ b/README.md @@ -198,54 +198,55 @@ can easily configure them for you specific needs. FT245 FT6X06 +GPIO-SAMPLER HCLAx HD44780 HMC58x HMC6343 -HX711 +HX711 I2C-EEPROM ITG3200 L3GD20 LAWICEL LIS302DL -LIS3DSH +LIS3DSH LIS3MDL LM75 LP503X LSM303A LSM6DS33 -LTC2984 +LTC2984 MAX6966 MAX7219 MCP23X17 MCP2515 NOKIA5110 -NRF24 +NRF24 TFT-DISPLAY PAT9125EL PCA8574 PCA9535 PCA9548A -PCA9685 +PCA9685 SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 SSD1306 -SX1276 +SX1276 TCS3414 TCS3472 TLC594X TMP102 TMP175 -VL53L0 +VL53L0 VL6180 WS2812 diff --git a/src/modm/driver/gpio/gpio_sampler.cpp.in b/src/modm/driver/gpio/gpio_sampler.cpp.in new file mode 100644 index 0000000000..e2f8942569 --- /dev/null +++ b/src/modm/driver/gpio/gpio_sampler.cpp.in @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018, 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 "gpio_sampler.hpp" +#include +#include +#include + +using IrqHandler = void(*)(); +%% if vectors_location == "rom" +modm_fastdata +IrqHandler exti_vectors[{{ extis | length }}] = {nullptr}; + +%% for vector in extis +MODM_ISR({{ vector }}) +{ + exti_vectors[{{ loop.index0 }}](); +} +%% endfor +%% endif + +const IRQn_Type +irq_map[{{ extis | length }}] = +{ +%% for vector in extis + {{ vector }}_IRQn, +%% endfor +}; + +namespace modm +{ + +modm_fastdata +void *GpioSampler::context{nullptr}; + +void +GpioSampler::reset(Interrupt vector) +{ + const size_t index = int(vector); + NVIC_DisableIRQ(irq_map[index]); + NVIC_SetPriority(irq_map[index], 0); +} + +void +GpioSampler::setHandler(Interrupt vector, IrqHandler handler) +{ + const size_t index = int(vector); + if (index >= {{ extis | length }}) return; +%% if vectors_location == "ram" + NVIC_SetVector(irq_map[index], (uint32_t) handler); +%% else + exti_vectors[index] = handler; +%% endif + NVIC_EnableIRQ(irq_map[index]); +} + +GpioSampler::Channel::Channel() +{} +void +GpioSampler::Channel::allocate(size_t max_samples) +{ + max_count = max_samples + 1; + data = new Type[max_samples + 1]; +} +GpioSampler::Channel::~Channel() +{ + delete[] data; +} + +void +GpioSampler::Channel::reset() +{ + count = 0; +} + +void +GpioSampler::Channel::dump() const +{ + for (size_t ii=0; ii 0); +} + +GpioSampler::Type +GpioSampler::Channel::diff(size_t index) const +{ + if (index == 0) return 0; + Type s0 = (*this)[index - 1]; + Type s1 = (*this)[index]; + // abs + uint32_t t0 = (s0 > 0) ? s0 : -s0; + uint32_t t1 = (s1 > 0) ? s1 : -s1; + // Fix overflow issues + if (t1 < t0) t1 |= (1ul << 31); + return t1 - t0; +} + +} // namespace modm::platform diff --git a/src/modm/driver/gpio/gpio_sampler.hpp.in b/src/modm/driver/gpio/gpio_sampler.hpp.in new file mode 100644 index 0000000000..738e06428f --- /dev/null +++ b/src/modm/driver/gpio/gpio_sampler.hpp.in @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018, 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/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_TEST_LOGIC_ANALYZER_SAMPLER_HPP +#define MODM_TEST_LOGIC_ANALYZER_SAMPLER_HPP + +#include +#include +#include + +namespace modm +{ + +class GpioSampler +{ +public: + using Type = int32_t; + + class Channel + { + friend class GpioSampler; + size_t max_count = 0; + volatile size_t count = 0; + Type *data = nullptr; + Channel(); + void allocate(size_t max_samples); + void add(Type time); + void reset(); + + public: + ~Channel(); + + inline size_t max() const { return max_count; } + inline size_t size() const { return count; } + + void dump() const; + + Type diff(size_t index) const; + bool read(size_t index) const; + + const Type& operator[](size_t index) const; + + inline const Type* begin() const { return data; } + inline const Type* end() const { return &data[count]; } + }; + + template + class Handle + { + friend class GpioSampler; + using CleanupHandler = void(*)(); + using StartHandler = void(*)(Handle &); + + const CleanupHandler cleanup; + const StartHandler start; + Channel data[channels]; + + Handle(size_t max_samples, StartHandler start, CleanupHandler cleanup); + void set_start_time(const Type *start); + public: + static constexpr size_t Channels = channels; + + public: + ~Handle(); + + void + restart(); + + const Channel& + operator[](size_t channel) const; + }; + + template< class... Gpios > + static auto Create(size_t max_samples); + +protected: + static void *context; + + enum class + Interrupt : uint8_t + { +%% for vector in extis + {{ vector | capitalize }} = {{ loop.index0 }}, +%% endfor + }; + + template< size_t channels, size_t pin_count, uint8_t pin, class Gpio, class... Gpios > + static void sampleGpio(Channel *data, Type time); + template< size_t channels, size_t pin_count, uint8_t pin > + static void sampleGpio(Channel *, Type) {} + + static void reset(Interrupt vector); + static void setHandler(Interrupt vector, void(*handler)()); + static inline Type getTime() { + return Type(DWT->CYCCNT & ~(1ul << 31)); + } +}; + +} // namespace modm + +#include "gpio_sampler_impl.hpp" + +#endif // MODM_TEST_LOGIC_ANALYZER_SAMPLER_HPP diff --git a/src/modm/driver/gpio/gpio_sampler.lb b/src/modm/driver/gpio/gpio_sampler.lb new file mode 100644 index 0000000000..0db3993b80 --- /dev/null +++ b/src/modm/driver/gpio/gpio_sampler.lb @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, 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/. + +from collections import OrderedDict + +def init(module): + module.parent = ":driver" + module.name = "gpio_sampler" + + +def prepare(module, options): + if options[":target"].identifier["platform"] != "stm32": + return False + + core = options[":target"].get_driver("core:cortex-m*") + # Cortex-M0 doesn't have the DWT->CYCCNT and Cortex-M7 support is broken + if not core or "m0" in core["type"] or "m7" in core["type"]: + return False + + module.depends( + ":platform:gpio", + ":platform:core", + ":architecture:interrupt") + return True + + +def build(env): + exti_vectors = [v["name"] for v in env[":target"].get_driver("core")["vector"] if "EXTI" in v["name"]] + # These are all exti possible vectors: 0, 0_1, 1, 15_10, 2, 2_3, 2_TSC, 3, 4, 4_15, 9_5 + extimap = { + "0": [0], "1": [1], "2": [2], "3": [3], "4": [4], + "0_1": [0,1], + "2_TSC": [2], + "2_3": [2,3], + "4_15": [4,5,6,7,8,9,10,11,12,13,14,15], + "9_5": [5,6,7,8,9], + "15_10": [10,11,12,13,14,15], + } + extis = OrderedDict() + for v in sorted(exti_vectors): + extis[v] = extimap[v[4:]] + + env.substitutions = { + "extis": extis, + "vectors_location": env.get(":platform:core:vector_table_location", "rom") + } + env.outbasepath = "modm/src/modm/driver" + env.template("gpio_sampler.cpp.in") + env.template("gpio_sampler.hpp.in") + env.template("gpio_sampler_impl.hpp.in") diff --git a/src/modm/driver/gpio/gpio_sampler_impl.hpp.in b/src/modm/driver/gpio/gpio_sampler_impl.hpp.in new file mode 100644 index 0000000000..1945fbc832 --- /dev/null +++ b/src/modm/driver/gpio/gpio_sampler_impl.hpp.in @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018, 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/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_TEST_LOGIC_ANALYZER_SAMPLER_HPP +# error "Don't include this file directly, use 'gpio_sampler.hpp' instead!" +#endif + +#include +#include +#include + +namespace modm +{ + +template +GpioSampler::Handle::~Handle() +{ + cleanup(); + GpioSampler::context = nullptr; +} + +template +GpioSampler::Handle::Handle(size_t max_samples, StartHandler start, CleanupHandler cleanup): + cleanup(cleanup), start(start) +{ + for (auto &ch : data) ch.allocate(max_samples); +} + +template +void +GpioSampler::Handle::set_start_time(const Type *start) +{ + for (size_t ii=0; ii +void +GpioSampler::Handle::restart() +{ + modm::atomic::Lock l; + for (auto &ch : data) ch.reset(); + start(*this); +} + +template +const GpioSampler::Channel& +GpioSampler::Handle::operator[](size_t channel) const +{ + return data[(channel < channels) ? channel : (channels - 1)]; +} + +template< size_t channels, size_t pin_count, uint8_t pin, class Gpio, class... Gpios > +void +GpioSampler::sampleGpio(Channel *data, Type time) +{ + if constexpr (Gpio::pin == pin) + { + if ((pin_count == 1) or Gpio::getExternalInterruptFlag()) + { + if (not Gpio::read()) time = -time; + constexpr size_t channel = channels - sizeof...(Gpios) - 1; + data[channel].add(time); + Gpio::acknowledgeExternalInterruptFlag(); + } + } + sampleGpio(data, time); +} + +template< class... Gpios > +auto +GpioSampler::Create(size_t max_samples) +{ + // Count the pins per interrupt and + // only reset the vectors that contain at least one GPIO +%% for vector, pos_map in extis.items() + constexpr size_t pin_count_{{vector | lower}} = +%% for pos in pos_map + ((Gpios::pin == {{pos}} ? 1 : 0) + ...){% if loop.last %};{% else %} +{% endif %} +%% endfor + if constexpr (pin_count_{{vector | lower}}) reset(Interrupt::{{ vector | capitalize }}); +%% endfor + + Handle handle(max_samples, + [](Handle &h) { + // prime the data logger + const Type time = getTime(); + const Type start[] = {(Gpios::read() ? time : -time)...}; + h.set_start_time(start); + }, + []() { + (Gpios::disableExternalInterrupt(), ...); + // only reset the vectors that contain at least one GPIO +%% for vector in extis + if constexpr (pin_count_{{vector | lower}}) reset(Interrupt::{{ vector | capitalize }}); +%% endfor + }); + GpioSampler::context = reinterpret_cast(&handle); +%% for vector, pos_map in extis.items() + // only reset the vectors that contain at least one GPIO + if constexpr (pin_count_{{vector | lower}}) { + setHandler(Interrupt::{{ vector | capitalize }}, []() + { + const Type time = getTime(); + auto *h = reinterpret_cast*>(GpioSampler::context); + %% for pos in pos_map + sampleGpio< sizeof...(Gpios), pin_count_{{vector | lower}}, {{pos}}, Gpios... >(h->data, time); + %% endfor + }); + } +%% endfor + // trigger on both edges + (Gpios::setInputTrigger(Gpios::InputTrigger::BothEdges), ...); + // enabled all GPIO interrupts + (Gpios::enableExternalInterrupt(), ...); + // prime the logger with initial measurement + handle.restart(); + return handle; +} + +} // namespace modm diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index 8e611b1e04..5aa6d5db57 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -20,6 +20,7 @@ /// @cond namespace modm::platform { +uint32_t modm_fastdata fcpu({{ "{0:,}".format((1e6 * hsi_frequency)|int).replace(',', "'") }}); uint16_t modm_fastdata delay_fcpu_MHz({{ hsi_frequency }}); uint16_t modm_fastdata delay_ns_per_loop({{ "{0:,}".format((loops * 1000.0 / hsi_frequency)|int).replace(',', "'") }}); } diff --git a/src/modm/platform/clock/stm32/rcc_impl.hpp.in b/src/modm/platform/clock/stm32/rcc_impl.hpp.in index b534d4505e..bbf53c5d9e 100644 --- a/src/modm/platform/clock/stm32/rcc_impl.hpp.in +++ b/src/modm/platform/clock/stm32/rcc_impl.hpp.in @@ -14,6 +14,7 @@ namespace modm::platform { /// @cond +extern uint32_t fcpu; extern uint16_t delay_fcpu_MHz; extern uint16_t delay_ns_per_loop; /// @endcond @@ -96,6 +97,7 @@ void Rcc::updateCoreFrequency() { delay_fcpu_MHz = Core_Hz / 1'000'000; + fcpu = Core_Hz; delay_ns_per_loop = std::round({{loops}}000.f / (Core_Hz / 1'000'000)); } From 30e4f462486a062a5b4583c1ded23ce25b296477 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 7 Oct 2018 20:30:21 +0200 Subject: [PATCH 3/5] [test] Add GpioSampler test --- test/modm/platform/gpio/module.lb | 14 ++- .../gpio/platform_gpio_test_avr.cpp.in | 4 +- .../gpio/platform_gpio_test_stm32.cpp.in | 6 +- .../gpio_sampler/gpio_sampler_test.cpp | 90 +++++++++++++++++++ .../gpio_sampler/gpio_sampler_test.hpp | 22 +++++ test/modm/platform/gpio_sampler/module.lb | 44 +++++++++ 6 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 test/modm/platform/gpio_sampler/gpio_sampler_test.cpp create mode 100644 test/modm/platform/gpio_sampler/gpio_sampler_test.hpp create mode 100644 test/modm/platform/gpio_sampler/module.lb diff --git a/test/modm/platform/gpio/module.lb b/test/modm/platform/gpio/module.lb index d408d1f8ca..da6d1b4b6b 100644 --- a/test/modm/platform/gpio/module.lb +++ b/test/modm/platform/gpio/module.lb @@ -79,8 +79,18 @@ def init(module): module.name = ":test:platform:gpio" def prepare(module, options): - target = options[":target"].identifier - if target["platform"] in ["stm32"]: + target = options[":target"] + core = target.get_driver("core:cortex-m*") + # Cortex-M0 doesn't have the DWT->CYCCNT and Cortex-M7 support is broken + if not core or "m0" in core["type"] or "m7" in core["type"]: + return False + if target.identifier.platform != "stm32": + return False + # Only 64-pin TQFP package + if target.identifier.pin != "r" or target.identifier.package != "t": + return False + + if target.identifier.platform in ["stm32"]: module.add_option( BooleanOption( name="test_gpio_lock", diff --git a/test/modm/platform/gpio/platform_gpio_test_avr.cpp.in b/test/modm/platform/gpio/platform_gpio_test_avr.cpp.in index 4557fb0ce3..ea2ee2220e 100644 --- a/test/modm/platform/gpio/platform_gpio_test_avr.cpp.in +++ b/test/modm/platform/gpio/platform_gpio_test_avr.cpp.in @@ -79,7 +79,7 @@ PlatformGpioTest::testInput() { %% for (port, pin) in test_io Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullUp); - modm::delayMilliseconds(1); + modm::delay_ms(1); TEST_ASSERT_TRUE((PIN{{port}} & (1ul << {{pin}})) == (1ul << {{pin}})); TEST_ASSERT_TRUE(Gpio{{port ~ pin}}::read()); %% endfor @@ -98,4 +98,4 @@ PlatformGpioTest::testConnect() void PlatformGpioTest::testLock() { -} \ No newline at end of file +} diff --git a/test/modm/platform/gpio/platform_gpio_test_stm32.cpp.in b/test/modm/platform/gpio/platform_gpio_test_stm32.cpp.in index 4eda10b8ec..b595d2b0d6 100644 --- a/test/modm/platform/gpio/platform_gpio_test_stm32.cpp.in +++ b/test/modm/platform/gpio/platform_gpio_test_stm32.cpp.in @@ -171,11 +171,11 @@ PlatformGpioTest::testInput() { %% for (port, pin) in test_io Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullUp); - modm::delayMilliseconds(1); + modm::delay_ms(1); TEST_ASSERT_TRUE((GPIO{{port}}->IDR & (1ul << {{pin}})) == (1ul << {{pin}})); TEST_ASSERT_TRUE(Gpio{{port ~ pin}}::read()); Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullDown); - modm::delayMilliseconds(1); + modm::delay_ms(1); TEST_ASSERT_TRUE((GPIO{{port}}->IDR & (1ul << {{pin}})) == 0ul); TEST_ASSERT_FALSE(Gpio{{port ~ pin}}::read()); %% endfor @@ -244,4 +244,4 @@ PlatformGpioTest::testLock() TEST_ASSERT_TRUE((GPIO{{port}}->LCKR & (1ul << {{pin}})) == (1ul << {{pin}})); %% endfor %% endif -} \ No newline at end of file +} diff --git a/test/modm/platform/gpio_sampler/gpio_sampler_test.cpp b/test/modm/platform/gpio_sampler/gpio_sampler_test.cpp new file mode 100644 index 0000000000..8c53230d3c --- /dev/null +++ b/test/modm/platform/gpio_sampler/gpio_sampler_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, 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 "gpio_sampler_test.hpp" + +#include + +using namespace modm::platform; +using namespace std::chrono_literals; + +void +GpioSamplerTest::testTiming() +{ + using Gpio0 = GpioA0; + using Gpio1 = GpioA1; + Gpio0::setOutput(modm::Gpio::Low); + Gpio1::setOutput(modm::Gpio::High); + + { + auto r = modm::GpioSampler::Create(100); + Gpio0::set(); modm::delay(1ms); + Gpio0::reset(); modm::delay(1ms); + Gpio0::set(); modm::delay(1ms); + Gpio0::reset(); modm::delay(1ms); + + Gpio1::reset(); modm::delay(1ms); + Gpio1::set(); modm::delay(1ms); + Gpio1::reset(); modm::delay(1ms); + Gpio1::set(); modm::delay(1ms); + + TEST_ASSERT_EQUALS(r[0].size(), 5u); + TEST_ASSERT_EQUALS(r[1].size(), 5u); + + MODM_LOG_INFO << "Channel 0:" << modm::endl; + for (const auto &t : r[0]) { + MODM_LOG_INFO << t << modm::endl; + } + MODM_LOG_INFO << "Channel 1:" << modm::endl;; + for (const auto &t : r[1]) { + MODM_LOG_INFO << t << modm::endl; + } + + TEST_ASSERT_FALSE(r[0].read(0)); // low + TEST_ASSERT_TRUE( r[0].read(1)); // high + TEST_ASSERT_FALSE(r[0].read(2)); // low + TEST_ASSERT_TRUE( r[0].read(3)); // high + TEST_ASSERT_FALSE(r[0].read(4)); // low + + TEST_ASSERT_TRUE( r[1].read(0)); // high + TEST_ASSERT_FALSE(r[1].read(1)); // low + TEST_ASSERT_TRUE( r[1].read(2)); // high + TEST_ASSERT_FALSE(r[1].read(3)); // low + TEST_ASSERT_TRUE( r[1].read(4)); // high + + const int32_t ce = modm::platform::fcpu / 1000; + const int32_t cu = ce * 1.1f; + + TEST_ASSERT_EQUALS(r[0].diff(0), 0); + TEST_ASSERT_EQUALS_RANGE(r[0].diff(1), 0l, ce); + TEST_ASSERT_EQUALS_RANGE(r[0].diff(2), ce, cu); + TEST_ASSERT_EQUALS_RANGE(r[0].diff(3), ce, cu); + TEST_ASSERT_EQUALS_RANGE(r[0].diff(4), ce, cu); + + TEST_ASSERT_EQUALS(r[1].diff(0), 0); + TEST_ASSERT_EQUALS_RANGE(r[1].diff(1), ce*4, ce*3 + cu); + TEST_ASSERT_EQUALS_RANGE(r[1].diff(2), ce, cu); + TEST_ASSERT_EQUALS_RANGE(r[1].diff(3), ce, cu); + TEST_ASSERT_EQUALS_RANGE(r[1].diff(4), ce, cu); + } +} + +void +GpioSamplerTest::testVerifier() +{ + +} + + diff --git a/test/modm/platform/gpio_sampler/gpio_sampler_test.hpp b/test/modm/platform/gpio_sampler/gpio_sampler_test.hpp new file mode 100644 index 0000000000..2f3e65f445 --- /dev/null +++ b/test/modm/platform/gpio_sampler/gpio_sampler_test.hpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018, 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 + +class GpioSamplerTest : public unittest::TestSuite +{ +public: + void + testTiming(); + + void + testVerifier(); +}; diff --git a/test/modm/platform/gpio_sampler/module.lb b/test/modm/platform/gpio_sampler/module.lb new file mode 100644 index 0000000000..0c6b36c9bb --- /dev/null +++ b/test/modm/platform/gpio_sampler/module.lb @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, 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/. + +from collections import OrderedDict + +def init(module): + module.parent = ":test:platform" + module.name = "gpio_sampler" + + +def prepare(module, options): + target = options[":target"] + core = target.get_driver("core:cortex-m*") + # Cortex-M0 doesn't have the DWT->CYCCNT and Cortex-M7 support is broken + if not core or "m0" in core["type"] or "m7" in core["type"]: + return False + if target.identifier.platform != "stm32": + return False + # Only 64-pin TQFP package + if target.identifier.pin != "r" or target.identifier.package != "t": + return False + + module.depends(":driver:gpio_sampler", ":mock:logic_analyzer") + return True + + +def build(env): + if not env.has_module(":board:nucleo-*"): + env.log.warn("`:test:platform:gpio_sampler` has been hardcoded to a Nucleo-64 board!") + # When porting make sure this test does not damage your board! + return + + env.outbasepath = "modm-test/src/modm-test/platform/gpio" + # The tests for the implementation + env.copy("gpio_sampler_test.hpp") + env.copy("gpio_sampler_test.cpp") From 062e1b7ac870f7599ecd8397968b73df2b3c3764 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Mon, 8 Oct 2018 23:29:26 +0200 Subject: [PATCH 4/5] [test] Add LogicAnalyzer implementation --- test/modm/mock/logic_analyzer.hpp | 127 ++++++++++++++++++++++++++++++ test/modm/mock/module.lb | 18 +++++ 2 files changed, 145 insertions(+) create mode 100644 test/modm/mock/logic_analyzer.hpp diff --git a/test/modm/mock/logic_analyzer.hpp b/test/modm/mock/logic_analyzer.hpp new file mode 100644 index 0000000000..8f390e2f21 --- /dev/null +++ b/test/modm/mock/logic_analyzer.hpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018, 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 + +namespace modm_test +{ + + +class LogicAnalyzer +{ +public: + using FailureHandler = std::function; + + static inline bool + verify(const char *state_machine, + const modm::GpioSampler::Channel &ch, + FailureHandler failure = nullptr) + { + const auto fn_fail = [&ch](const char *state, size_t index, int32_t time) + { + MODM_LOG_ERROR << "failed at '" << *state << "' with '" << ch[index] << "'"; + if (time) { MODM_LOG_ERROR << " at diff " << ch.diff(index) << " '" << time << "'"; } + MODM_LOG_ERROR << modm::endl; + }; + if (failure == nullptr) failure = fn_fail; + + const char *state = state_machine; + const char *repeat = nullptr; + size_t repeat_count = 0; + + int32_t tmin = 0; + int32_t tmax = INT_MAX; + volatile int32_t *time = &tmin; + + size_t sidx = 0; + + while(*state) + { + switch(*state) + { + case 'l': // low sample + case 'h': // high sample + { + if (int32_t diff = ch.diff(sidx); diff) { + if (diff < tmin) failure(state, sidx, -tmin); + if (tmax < diff) failure(state, sidx, tmax); + } + + int32_t sample = ch[sidx]; + if (*state == 'l') { + if (0 < sample) failure(state, sidx, 0); + // MODM_LOG_DEBUG << "l " << sample << modm::endl; + } else { + if (sample < 0) failure(state, sidx, 0); + // MODM_LOG_DEBUG << "h " << sample << modm::endl; + } + time = &tmin; + break; + } + case '>': // next sample + if (sidx >= ch.max()) failure(state, sidx, 0); + time = &tmax; + sidx++; + // MODM_LOG_DEBUG << '>' << modm::endl; + break; + + case '0'...'9': // timing information + { + char *out; + const uint64_t us = strtoul(state, &out, 10); + const uint32_t cycles = (modm::platform::fcpu * us) / 1000000; + *time = cycles; + state = out; + // MODM_LOG_DEBUG << (time == &tmin ? "min " : "max ") << us << "us => " << cycles << modm::endl; + continue; + } + + case '{': // start of repeat count + { + char *out; + repeat_count = strtoul(state+1, &out, 10) - 1; + state = out; + // MODM_LOG_DEBUG << '{' << repeat_count << '}' << modm::endl; + continue; + } + case '}': // end of repeat count + break; + + case '(': // start of repeat group + repeat = state; + // MODM_LOG_DEBUG << '(' << modm::endl; + break; + + case ')': // end of repeat group + if (repeat_count > 0) { + repeat_count--; + state = repeat; + // MODM_LOG_DEBUG << ")<-" << repeat_count << modm::endl; + continue; + } + break; + + default: // just continue + break; + } + state++; + } + return true; + } +}; + +} // namespace modm::platform diff --git a/test/modm/mock/module.lb b/test/modm/mock/module.lb index 03ec138194..e2c46c094e 100644 --- a/test/modm/mock/module.lb +++ b/test/modm/mock/module.lb @@ -74,6 +74,23 @@ class SharedMedium(Module): env.outbasepath = "modm-test/src/modm-test/mock" env.copy("shared_medium.hpp") +class LogicAnalyzer(Module): + def init(self, module): + module.name = "logic_analyzer" + + def prepare(self, module, options): + core = options[":target"].get_driver("core:cortex-m*") + # Cortex-M0 doesn't have the DWT->CYCCNT and Cortex-M7 support is broken + if not core or "m0" in core["type"] or "m7" in core["type"]: + return False + + module.depends(":stdc++", ":driver:gpio_sampler", ":debug") + return True + + def build(self, env): + env.outbasepath = "modm-test/src/modm-test/mock" + env.copy("logic_analyzer.hpp") + def init(module): module.name = ":mock" @@ -84,6 +101,7 @@ def prepare(module, options): module.add_submodule(SpiMaster()) module.add_submodule(IoDevice()) module.add_submodule(SharedMedium()) + module.add_submodule(LogicAnalyzer()) return True def build(env): From 9ab15d271ab7da371958d40ae97dfccad1d64ca7 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sun, 7 Oct 2018 20:29:22 +0200 Subject: [PATCH 5/5] [test] Add BitBang SpiMaster test --- test/modm/bitbang/module.lb | 45 +++++++++++++++ test/modm/bitbang/spi_bitbang_test.cpp | 77 ++++++++++++++++++++++++++ test/modm/bitbang/spi_bitbang_test.hpp | 19 +++++++ 3 files changed, 141 insertions(+) create mode 100644 test/modm/bitbang/module.lb create mode 100644 test/modm/bitbang/spi_bitbang_test.cpp create mode 100644 test/modm/bitbang/spi_bitbang_test.hpp diff --git a/test/modm/bitbang/module.lb b/test/modm/bitbang/module.lb new file mode 100644 index 0000000000..a6d3a3d734 --- /dev/null +++ b/test/modm/bitbang/module.lb @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, 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/. + + +def init(module): + module.parent = ":test:platform" + module.name = "spi.bitbang" + + +def prepare(module, options): + target = options[":target"] + core = target.get_driver("core:cortex-m*") + # Cortex-M0 doesn't have the DWT->CYCCNT and Cortex-M7 support is broken + if not core or "m0" in core["type"] or "m7" in core["type"]: + return False + if target.identifier.platform != "stm32": + return False + # Only 64-pin TQFP package + if target.identifier.pin != "r" or target.identifier.package != "t": + return False + + module.depends( + ":platform:spi.bitbang", + ":driver:gpio_sampler") + return True + + +def build(env): + if not env.has_module(":board:nucleo-*"): + env.log.warn("`:test:platform:spi.bitbang` has been hardcoded to a Nucleo-64 board!") + # When porting make sure this test does not damage your board! + return + + global tim_instance + env.outbasepath = "modm-test/src/modm-test/platform/spi" + env.copy("spi_bitbang_test.hpp") + env.copy("spi_bitbang_test.cpp") diff --git a/test/modm/bitbang/spi_bitbang_test.cpp b/test/modm/bitbang/spi_bitbang_test.cpp new file mode 100644 index 0000000000..5d4bd8c288 --- /dev/null +++ b/test/modm/bitbang/spi_bitbang_test.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018, 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 "spi_bitbang_test.hpp" +#include +#include +#include + +#include + +using namespace modm::platform; + + + +void +SpiBitbangTest::testSpiMaster() +{ + using Sck = GpioA0; + using Mosi = GpioA1; + using SpiMaster = BitBangSpiMaster; + SpiMaster::connect(); + SpiMaster::initialize(); + + const auto Start = modm::GpioSampler::Create; + + { + auto r = Start(50); + SpiMaster::transferBlocking(0); + TEST_ASSERT_EQUALS(r[1].size(), 1u); + TEST_ASSERT_EQUALS(r[0].size(), 17u); + + modm_test::LogicAnalyzer::verify("l", r[1]); // MOSI + modm_test::LogicAnalyzer::verify("l {8}( > h 45>55 l )", r[0]); // SCK + + r[0].dump(); + r[1].dump(); + + r.restart(); + SpiMaster::transferBlocking(0b1010'1010); + TEST_ASSERT_EQUALS(r[1].size(), 9u); + TEST_ASSERT_EQUALS(r[0].size(), 17u); + + modm_test::LogicAnalyzer::verify("l {4}( > h 95>110 l ) ", r[1]); // MOSI + modm_test::LogicAnalyzer::verify("l {8}( > h 45>55 l )", r[0]); // SCK + + r[1].dump(); + + r.restart(); + SpiMaster::transferBlocking(0b0001'1110); + TEST_ASSERT_EQUALS(r[1].size(), 3u); + TEST_ASSERT_EQUALS(r[0].size(), 17u); + + modm_test::LogicAnalyzer::verify("l 295> h >420 l ", r[1]); // MOSI + modm_test::LogicAnalyzer::verify("l {8}( > h 45>55 l )", r[0]); // SCK + + r[1].dump(); + + r.restart(); + SpiMaster::setDataOrder(SpiMaster::DataOrder::LsbFirst); + SpiMaster::transferBlocking(0b0001'1110); + TEST_ASSERT_EQUALS(r[1].size(), 3u); + TEST_ASSERT_EQUALS(r[0].size(), 17u); + + r[1].dump(); + + modm_test::LogicAnalyzer::verify("l 95> h >420 l ", r[1]); // MOSI + modm_test::LogicAnalyzer::verify("l {8}( > h 45>55 l )", r[0]); // SCK + } +} diff --git a/test/modm/bitbang/spi_bitbang_test.hpp b/test/modm/bitbang/spi_bitbang_test.hpp new file mode 100644 index 0000000000..e9e397c5c9 --- /dev/null +++ b/test/modm/bitbang/spi_bitbang_test.hpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018, 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 + +class SpiBitbangTest : public unittest::TestSuite +{ +public: + void + testSpiMaster(); +};