From e41beec8daff2177a8af882200f23eb5d3bc931b Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Thu, 24 May 2018 00:38:41 +0200 Subject: [PATCH] [test] Add STM32 GPIO test Hardcoded for free IOs on STM32F469NI-DISCO board. Test will only be included if the `board.f469ni-disco` module is available. --- test/modm/platform/gpio/module.lb | 113 ++++++++ .../platform/gpio/platform_gpio_test.cpp.in | 250 ++++++++++++++++++ .../modm/platform/gpio/platform_gpio_test.hpp | 46 ++++ test/modm/platform/module.lb | 3 +- 4 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 test/modm/platform/gpio/module.lb create mode 100644 test/modm/platform/gpio/platform_gpio_test.cpp.in create mode 100644 test/modm/platform/gpio/platform_gpio_test.hpp diff --git a/test/modm/platform/gpio/module.lb b/test/modm/platform/gpio/module.lb new file mode 100644 index 0000000000..5c54e20914 --- /dev/null +++ b/test/modm/platform/gpio/module.lb @@ -0,0 +1,113 @@ +#!/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 defaultdict, OrderedDict + +# These tests have been hardcoded for the Nucleo-64 boards due to +# lack of some data-driven method for now. +# According to the schematic, the following pins are safe to use as IO and are +# not connected to anything else _on_ the board: +test_io = sorted([ + ("A", 0), # A0 + ("A", 1), # A1 + ("A", 4), # A2 + ("B", 0), # A3 + ("C", 1), # A4 + ("C", 0), # A5 + # ("A", 3), # D0 UART + # ("A", 2), # D1 UART + ("A", 10), # D2 + # ("B", 3), # D3 SWO + ("B", 5), # D4 + # ("B", 4), # D5 ??? + ("B", 10), # D6 + ("A", 8), # D7 + ("A", 9), # D8 + ("C", 7), # D9 + ("B", 6), # D10 + ("A", 7), # D11 + ("A", 6), # D12 + # ("A", 5), # D13 LED! + ("B", 9), # D14 + ("B", 8), # D15 +]) + +def translate(s): + name = "" + for part in s.split("_"): + name += part.capitalize() + return name + +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 get_name(s): + return translate(s["name"]) + +def get_af(s): + af = None + if "af" in s: af = int(s["af"]); + return af + + +def init(module): + module.name = "gpio" + module.parent = "test:platform" + +def prepare(module, options): + target = options[":target"].identifier + if target["platform"] not in ["stm32"]: + return False + + module.add_option( + BooleanOption( + name="test_gpio_lock", + description="Test locking the GPIOs. This will make the GPIOs unusable until the next reboot!", + default=False)) + + module.depends(":architecture:delay", ":platform:gpio") + return True + +def build(env): + if not env.has_module(":board.nucleo-*"): + env.log.warn("GPIO test has been hardcoded to a Nucleo-64 board. " + "When porting make sure this test does not damage your board!") + return + + device = env[":target"] + driver = device.get_driver("gpio") + gpios = [g for g in driver["gpio"] if (g["port"].upper(), int(g["pin"])) in test_io] + peripherals = sorted([get_driver(s) for g in gpios for s in (g["signal"] if "signal" in g else [])]) + connections = OrderedDict() + for k in peripherals: + rconns = [(g["port"].upper(), int(g["pin"]), get_name(s), get_af(s)) for g in gpios for s in (g["signal"] if "signal" in g else []) if get_driver(s) == k] + # we need to remove duplicate GPIOs from this list + # since we cannot set the same GPIO to multiple different AFs! + conns = [] + for r in rconns: + if any(r[0] == t[0] and r[1] == t[1] for t in conns): + continue + conns.append(r) + connections[k] = conns + + properties = { + "target": device.identifier, + "test_io": test_io, + "test_connections": connections + } + + env.substitutions = properties + env.outbasepath = "modm/test/modm/platform/gpio" + env.copy("platform_gpio_test.hpp") + env.template("platform_gpio_test.cpp.in") diff --git a/test/modm/platform/gpio/platform_gpio_test.cpp.in b/test/modm/platform/gpio/platform_gpio_test.cpp.in new file mode 100644 index 0000000000..dda299e815 --- /dev/null +++ b/test/modm/platform/gpio/platform_gpio_test.cpp.in @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2016, Antal Szabó + * Copyright (c) 2016, Kevin Läufer + * Copyright (c) 2017, Fabian Greif + * 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 "platform_gpio_test.hpp" + +#include + +using namespace modm::platform; + +void +PlatformGpioTest::setUp() +{ +%% for (port, pin) in test_io + // GPIO{{port}}->MODER &= ~(3ul << {{pin*2}}); GPIO{{port}}->OSPEEDR &= ~(3ul << {{pin*2}}); GPIO{{port}}->PUPDR &= ~(3ul << {{pin*2}}); + // GPIO{{port}}->OTYPER &= ~(1ul << {{pin}}); GPIO{{port}}->ODR &= ~(1ul << {{pin}}); GPIO{{port}}->AFR[{{pin//8}}] &= ~(15ul << {{(pin % 8) * 4}}); +%% endfor +} + +void +PlatformGpioTest::testEnabled() +{ +%% if options["modm:platform:gpio:enable_ports"] | length + // checks if the handler in enable.cpp has been called + // if this fails, then the function either wasn't found by the linker + // or the section was garbage collected, or the startup script has a bug +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE(RCC->APB2ENR & RCC_APB2ENR_AFIOEN); +%% else + TEST_ASSERT_TRUE(RCC->APB2ENR & RCC_APB2ENR_SYSCFGEN); +%% endif +%% if target["family"] in ["f2","f4", "f7"] + TEST_ASSERT_TRUE(SYSCFG->CMPCR & SYSCFG_CMPCR_CMP_PD); +%% endif + // check all ports are clock enabled and all GPIOs are reset +%% for (port, pin) in test_io +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0100ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->PUPDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->AFR[{{pin//8}}] & (0xful << {{(pin % 8) * 4}})) == 0ul); +%% endif + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->BSRR & (0x10001ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->LCKR & (1ul << {{pin}})) == 0ul); +%% endfor +%% endif +} + +void +PlatformGpioTest::testSetOutput() +{ +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::Low); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0010ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); +%% endif +%% endfor + +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setOutput(Gpio::OutputType::OpenDrain, Gpio::OutputSpeed::Medium); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0101ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == (1ul << {{pin}})); +%% endif +%% endfor + +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::High); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0011ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == (2ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); +%% endif +%% endfor + +%% if target["family"] in ["f2", "f4", "f7", "l4"] +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::VeryHigh); + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == (3ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); +%% endfor +%% endif + // configure() and setOutput() method are already tested in the above code paths +} + +void +PlatformGpioTest::testSetInput() +{ +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setInput(Gpio::InputType::Floating); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0100ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->PUPDR & (3ul << {{pin*2}})) == 0ul); +%% endif +%% endfor + +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullUp); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b1000ul << {{(pin % 8) * 4}})); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == (1ul << {{pin}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->PUPDR & (3ul << {{pin*2}})) == (1ul << {{pin*2}})); +%% endif +%% endfor + +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullDown); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b1000ul << {{(pin % 8) * 4}})); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == 0ul); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OSPEEDR & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->OTYPER & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->PUPDR & (3ul << {{pin*2}})) == (2ul << {{pin*2}})); +%% endif +%% endfor +} + +void +PlatformGpioTest::testOutput() +{ +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setOutput(); + Gpio{{port ~ pin}}::set(); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == (1ul << {{pin}})); + TEST_ASSERT_TRUE(Gpio{{port ~ pin}}::isSet()); + Gpio{{port ~ pin}}::reset(); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_FALSE(Gpio{{port ~ pin}}::isSet()); + Gpio{{port ~ pin}}::toggle(); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == (1ul << {{pin}})); + TEST_ASSERT_TRUE(Gpio{{port ~ pin}}::isSet()); + Gpio{{port ~ pin}}::toggle(); + TEST_ASSERT_TRUE((GPIO{{port}}->ODR & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_FALSE(Gpio{{port ~ pin}}::isSet()); +%% endfor +} + +void +PlatformGpioTest::testInput() +{ +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setInput(Gpio::InputType::PullUp); + modm::delayMilliseconds(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); + TEST_ASSERT_TRUE((GPIO{{port}}->IDR & (1ul << {{pin}})) == 0ul); + TEST_ASSERT_FALSE(Gpio{{port ~ pin}}::read()); +%% endfor +} + +void +PlatformGpioTest::testAnalog() +{ +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::setAnalogInput(); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == 0ul); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (3ul << {{pin*2}})); + %% if target["family"] in ["l4"] and target["name"] in ["71", "75", "76", "85", "86"] + TEST_ASSERT_TRUE((GPIO{{port}}->ASCR & (1ul << {{pin}})) == (1ul << {{pin}})); + %% endif +%% endif + Gpio{{port ~ pin}}::disconnect(); +%% if target["family"] in ["f1"] + TEST_ASSERT_TRUE((GPIO{{port}}->CR{{"L" if pin<8 else "H"}} & (0xful << {{(pin % 8) * 4}})) == (0b0100ul << {{(pin % 8) * 4}})); +%% else + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + %% if target["family"] in ["l4"] and target["name"] in ["71", "75", "76", "85", "86"] + TEST_ASSERT_TRUE((GPIO{{port}}->ASCR & (1ul << {{pin}})) == 0ul); + %% endif +%% endif +%% endfor +} + +void +PlatformGpioTest::testConnect() +{ +%% if target["family"] not in ["f1"] +%% for peripheral, connections in test_connections.items() + %% if connections | length + using TestConnection{{peripheral}} = GpioConnector;{% else %},{% endif %} + %% endfor + TestConnection{{peripheral}}::connect(); + %% for (port, pin, signal, af) in connections + %% if af + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (2ul << {{pin*2}})); + TEST_ASSERT_TRUE((GPIO{{port}}->AFR[{{pin//8}}] & (15ul << {{(pin % 8) * 4}})) == ({{af}}ul << {{(pin % 8) * 4}})); + %% elif peripheral.startswith("Adc") or peripheral.startswith("Dac") + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == (3ul << {{pin*2}})); + %% endif + %% endfor + TestConnection{{peripheral}}::disconnect(); + %% for (port, pin, signal, af) in connections + TEST_ASSERT_TRUE((GPIO{{port}}->MODER & (3ul << {{pin*2}})) == 0ul); + TEST_ASSERT_TRUE((GPIO{{port}}->AFR[{{pin//8}}] & (0xful << {{(pin % 8) * 4}})) == 0ul); + %% endfor + %% endif +%% endfor +%% endif +} + +void +PlatformGpioTest::testLock() +{ +%% if options["::::test_gpio_lock"] +%% for (port, pin) in test_io + Gpio{{port ~ pin}}::lock(); + 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/platform_gpio_test.hpp b/test/modm/platform/gpio/platform_gpio_test.hpp new file mode 100644 index 0000000000..88fc9ff149 --- /dev/null +++ b/test/modm/platform/gpio/platform_gpio_test.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2009, Martin Rosekeit + * Copyright (c) 2009-2010, Fabian Greif + * Copyright (c) 2012, 2015, Niklas Hauser + * Copyright (c) 2016, Kevin Läufer + * + * 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 PlatformGpioTest : public unittest::TestSuite +{ +public: + virtual void + setUp() override; + + void + testEnabled(); + + void + testSetOutput(); + + void + testSetInput(); + + void + testOutput(); + + void + testInput(); + + void + testAnalog(); + + void + testConnect(); + + void + testLock(); +}; diff --git a/test/modm/platform/module.lb b/test/modm/platform/module.lb index 63cce09e28..0035dbdaea 100644 --- a/test/modm/platform/module.lb +++ b/test/modm/platform/module.lb @@ -26,4 +26,5 @@ def prepare(module, options): def build(env): env.outbasepath = "modm/test/modm/platform" - env.copy('.', ignore=env.ignore_patterns("*.lb")) + env.copy("can_bit_timings_test.hpp") + env.copy("can_bit_timings_test.cpp")