From a9496ad9f76880b447941768a9d0f86070ed8c41 Mon Sep 17 00:00:00 2001 From: Hugues Kamba Date: Tue, 8 Oct 2019 12:36:47 +0100 Subject: [PATCH 1/2] PmwOut: Add methods to suspend and resume PWM It is now possible to temporarily suspend PWM and safely preserve the duty cycle set. This functionality is needed to allow a device to enter deep sleep as a PWM instance prevents deep sleep in order for the timer it relies on to run so its output can be modified. The duty cycle configuration can be restored upon resuming from deep sleep. --- drivers/PwmOut.h | 27 +++++++++++++++++ drivers/source/PwmOut.cpp | 64 +++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/drivers/PwmOut.h b/drivers/PwmOut.h index 68102caaa5..aa067b37ba 100644 --- a/drivers/PwmOut.h +++ b/drivers/PwmOut.h @@ -118,6 +118,24 @@ public: */ void pulsewidth_us(int us); + /** Suspend PWM operation + * + * Control the PWM state. This is primarily intended + * for temporary power-saving; This call can + * allow pwm to be temporarily disabled to permit power saving without + * losing device state. The subsequent function call must be PwmOut::resume + * for PWM to resume; any other calls prior to resuming are undefined behavior. + */ + void suspend(); + + /** Resume PWM operation + * + * Control the PWM state. This is primarily intended + * to resume PWM operations after a previous PwmOut::suspend call; + * This call restores the device state prior to suspension. + */ + void resume(); + /** A operator shorthand for write() * \sa PwmOut::write() */ @@ -155,8 +173,17 @@ protected: /** Unlock deep sleep in case it is locked */ void unlock_deep_sleep(); + /** Initialize this instance */ + void init(); + + /** Power down this instance */ + void deinit(); + pwmout_t _pwm; + PinName _pin; bool _deep_sleep_locked; + bool _initialized; + float _duty_cycle; #endif }; diff --git a/drivers/source/PwmOut.cpp b/drivers/source/PwmOut.cpp index 67e5f36b34..2951bbe414 100644 --- a/drivers/source/PwmOut.cpp +++ b/drivers/source/PwmOut.cpp @@ -22,28 +22,28 @@ #include "platform/mbed_critical.h" #include "platform/mbed_power_mgmt.h" +#include "platform/mbed_assert.h" namespace mbed { -PwmOut::PwmOut(PinName pin) : _deep_sleep_locked(false) +PwmOut::PwmOut(PinName pin) : + _pin(pin), + _deep_sleep_locked(false), + _initialized(false), + _duty_cycle(0) { - core_util_critical_section_enter(); - pwmout_init(&_pwm, pin); - core_util_critical_section_exit(); + PwmOut::init(); } PwmOut::~PwmOut() { - core_util_critical_section_enter(); - pwmout_free(&_pwm); - unlock_deep_sleep(); - core_util_critical_section_exit(); + MBED_ASSERT(!_initialized); + PwmOut::deinit(); } void PwmOut::write(float value) { core_util_critical_section_enter(); - lock_deep_sleep(); pwmout_write(&_pwm, value); core_util_critical_section_exit(); } @@ -98,6 +98,26 @@ void PwmOut::pulsewidth_us(int us) core_util_critical_section_exit(); } +void PwmOut::suspend() +{ + core_util_critical_section_enter(); + if (_initialized) { + _duty_cycle = PwmOut::read(); + PwmOut::deinit(); + } + core_util_critical_section_exit(); +} + +void PwmOut::resume() +{ + core_util_critical_section_enter(); + if (!_initialized) { + PwmOut::init(); + PwmOut::write(_duty_cycle); + } + core_util_critical_section_exit(); +} + void PwmOut::lock_deep_sleep() { if (_deep_sleep_locked == false) { @@ -114,6 +134,32 @@ void PwmOut::unlock_deep_sleep() } } +void PwmOut::init() +{ + core_util_critical_section_enter(); + + if (!_initialized) { + pwmout_init(&_pwm, _pin); + lock_deep_sleep(); + _initialized = true; + } + + core_util_critical_section_exit(); +} + +void PwmOut::deinit() +{ + core_util_critical_section_enter(); + + if (_initialized) { + pwmout_free(&_pwm); + unlock_deep_sleep(); + _initialized = false; + } + + core_util_critical_section_exit(); +} + } // namespace mbed #endif // #if DEVICE_PWMOUT From b8bcc7faced0fd22d29d453f67628029a853d1f4 Mon Sep 17 00:00:00 2001 From: Hugues Kamba Date: Wed, 30 Oct 2019 09:25:48 +0000 Subject: [PATCH 2/2] PwmOut: Add unit test --- UNITTESTS/drivers/PwmOut/test_pwmout.cpp | 258 ++++++++++++++++++ UNITTESTS/drivers/PwmOut/unittest.cmake | 28 ++ UNITTESTS/stubs/pwmout_api_stub.c | 61 +++++ UNITTESTS/target_h/PinNames.h | 4 + UNITTESTS/target_h/objects.h | 7 + UNITTESTS/target_h/platform/mbed_power_mgmt.h | 5 + drivers/source/PwmOut.cpp | 1 - 7 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 UNITTESTS/drivers/PwmOut/test_pwmout.cpp create mode 100644 UNITTESTS/drivers/PwmOut/unittest.cmake create mode 100644 UNITTESTS/stubs/pwmout_api_stub.c diff --git a/UNITTESTS/drivers/PwmOut/test_pwmout.cpp b/UNITTESTS/drivers/PwmOut/test_pwmout.cpp new file mode 100644 index 0000000000..4ea807cc70 --- /dev/null +++ b/UNITTESTS/drivers/PwmOut/test_pwmout.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2019 Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "drivers/PwmOut.h" + +using namespace mbed; + +class MbedPowerMgmtInterface { +public: + virtual ~MbedPowerMgmtInterface() {} + virtual void sleep_manager_unlock_deep_sleep_internal() = 0; + virtual void sleep_manager_lock_deep_sleep_internal() = 0; +}; + + +class MockMbedPowerMgmt : public MbedPowerMgmtInterface { +public: + virtual ~MockMbedPowerMgmt() {} + + MOCK_METHOD0(sleep_manager_unlock_deep_sleep_internal, void()); + MOCK_METHOD0(sleep_manager_lock_deep_sleep_internal, void()); + + static MockMbedPowerMgmt *get_instance() + { + if (_instance == nullptr) { + _instance = new MockMbedPowerMgmt(); + } + + ++_ref_counter; + + return _instance; + } + + static void release_instance() + { + --_ref_counter; + + if ((_instance != nullptr) && (_ref_counter == 0)) { + delete _instance; + _instance = nullptr; + } + } + + static void sleep_manager_unlock_deep_sleep() + { + // Do not increment the call count unless it's done by a test + // case. + if (_instance) { + _instance->sleep_manager_unlock_deep_sleep_internal(); + } + } + + static void sleep_manager_lock_deep_sleep() + { + // Do not increment the call count unless it's done by a test + // case. + if (_instance) { + _instance->sleep_manager_lock_deep_sleep_internal(); + } + } + + MockMbedPowerMgmt(MockMbedPowerMgmt const &) = delete; + void operator=(MockMbedPowerMgmt const &) = delete; +private: + MockMbedPowerMgmt() {} + static MockMbedPowerMgmt *_instance; + static int _ref_counter; +}; +MockMbedPowerMgmt *MockMbedPowerMgmt::_instance = nullptr; +int MockMbedPowerMgmt::_ref_counter = 0; + + +void sleep_manager_unlock_deep_sleep(void) +{ + MockMbedPowerMgmt::sleep_manager_unlock_deep_sleep(); +} + + +void sleep_manager_lock_deep_sleep(void) +{ + MockMbedPowerMgmt::sleep_manager_lock_deep_sleep(); +} + + +class TestPwmOut : public testing::Test { +protected: + PwmOut *pwm_obj; + MockMbedPowerMgmt *mbed_mgmt_mock; + + void SetUp() + { + pwm_obj = new PwmOut(PTC1); + // Create a mock object singleton after the PwmOut object + // instantiation so the sleep_manager_lock_deep_sleep call by the + // constructor does not increment the call count. + // Now it is ok because a test case is about to start. + mbed_mgmt_mock = MockMbedPowerMgmt::get_instance(); + } + + void TearDown() + { + // Destroy the mock object singleton before the PwmOut destruction + // because it will increment the sleep_manager_unlock_deep_sleep call + // count. + MockMbedPowerMgmt::release_instance(); + delete pwm_obj; + } +}; +// *INDENT-ON* + + +/** Test if the constructor locks deepsleep + Given an instantiated Pmw + When the constructor is called + Then the deep sleep lock is acquired + */ +TEST_F(TestPwmOut, test_constructor) +{ + using ::testing::Mock; + + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_lock_deep_sleep_internal() + ).Times(1); + + PwmOut pmw(PTC1); + + // Force gMock to verify a mock object singleton before it is destructed + // by the teardown + Mock::VerifyAndClearExpectations(mbed_mgmt_mock); + // Suppress the memory leak error. The mock singleton will be released and + // deleted in the TearDown call. + Mock::AllowLeak(mbed_mgmt_mock); +} + + +/** Test if the destructor unlocks deepsleep + Given an instantiated Pmw + When the destructor is called + Then the deep sleep lock is released + */ +TEST_F(TestPwmOut, test_destructor) +{ + using ::testing::Mock; + + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_unlock_deep_sleep_internal() + ).Times(1); + + PwmOut *pmw = new PwmOut(PTC1); + delete pmw; + + // Force gMock to verify a mock object singleton before it is destructed + // by the teardown + Mock::VerifyAndClearExpectations(mbed_mgmt_mock); + // Suppress the memory leak error. The mock singleton will be released and + // deleted in the TearDown call. + Mock::AllowLeak(mbed_mgmt_mock); +} + + +/** Test if `suspend` unlocks deepsleep + + Given an initialised Pmw with a deep sleep lock + When the instance is suspended + Then the deep sleep lock is released once + */ +TEST_F(TestPwmOut, test_suspend) +{ + using ::testing::Mock; + + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_unlock_deep_sleep_internal() + ).Times(1); + pwm_obj->suspend(); + pwm_obj->suspend(); + + // Force gMock to verify a mock object singleton before it is destructed + // by the teardown + Mock::VerifyAndClearExpectations(mbed_mgmt_mock); + // Suppress the memory leak error. The mock singleton will be released and + // deleted in the TearDown call. + Mock::AllowLeak(mbed_mgmt_mock); +} + + +/** Test if `resume` lock deepsleep + + Given an initialised Pmw in a suspended state + When the instance is resumed + Then the deep sleep lock is re-acquired once + */ +TEST_F(TestPwmOut, test_resume) +{ + using ::testing::Mock; + + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_lock_deep_sleep_internal() + ).Times(1); + + pwm_obj->suspend(); + pwm_obj->resume(); + pwm_obj->resume(); + + // Force gMock to verify a mock object singleton before it is destructed + // by the teardown + Mock::VerifyAndClearExpectations(mbed_mgmt_mock); + // Suppress the memory leak error. The mock singleton will be released and + // deleted in the TearDown call. + Mock::AllowLeak(mbed_mgmt_mock); +} + +/** Test if `suspend`/`resume` unlock/lock deepsleep multiple times + + Given an initialised Pmw + When the instance is suspended and then resumed + Then the deep sleep lock can be unlocked and then locked again and again + */ +TEST_F(TestPwmOut, test_multiple_suspend_resume) +{ + using ::testing::Mock; + + const int suspend_resume_max_cycle = 3; + + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_unlock_deep_sleep_internal() + ).Times(suspend_resume_max_cycle); + EXPECT_CALL( + *mbed_mgmt_mock, sleep_manager_lock_deep_sleep_internal() + ).Times(suspend_resume_max_cycle); + + for (int i = 0; i < suspend_resume_max_cycle; i++) { + pwm_obj->suspend(); + pwm_obj->resume(); + } + + // Force gMock to verify a mock object singleton before it is destructed + // by the teardown + Mock::VerifyAndClearExpectations(mbed_mgmt_mock); + // Suppress the memory leak error. The mock singleton will be released and + // deleted in the TearDown call. + Mock::AllowLeak(mbed_mgmt_mock); +} diff --git a/UNITTESTS/drivers/PwmOut/unittest.cmake b/UNITTESTS/drivers/PwmOut/unittest.cmake new file mode 100644 index 0000000000..475cf0ae94 --- /dev/null +++ b/UNITTESTS/drivers/PwmOut/unittest.cmake @@ -0,0 +1,28 @@ + +#################### +# UNIT TESTS +#################### +set(TEST_SUITE_NAME "PwmOut") + +# Add test specific include paths +set(unittest-includes ${unittest-includes} + . + ../hal +) + +# Source files +set(unittest-sources + ../drivers/source/PwmOut.cpp +) + +# Test files +set(unittest-test-sources + drivers/PwmOut/test_pwmout.cpp + stubs/mbed_critical_stub.c + stubs/mbed_assert_stub.c + stubs/pwmout_api_stub.c +) + +# defines +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEVICE_PWMOUT") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEVICE_PWMOUT") diff --git a/UNITTESTS/stubs/pwmout_api_stub.c b/UNITTESTS/stubs/pwmout_api_stub.c new file mode 100644 index 0000000000..1eae62b0ca --- /dev/null +++ b/UNITTESTS/stubs/pwmout_api_stub.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Arm Limited and affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "hal/pwmout_api.h" + +#if DEVICE_PWMOUT + +void pwmout_init(pwmout_t *obj, PinName pin) +{ +} + +void pwmout_free(pwmout_t *obj) +{ +} + +void pwmout_write(pwmout_t *obj, float percent) +{ +} + +float pwmout_read(pwmout_t *obj) +{ + return 0; +} + +void pwmout_period(pwmout_t *obj, float seconds) +{ +} + +void pwmout_period_ms(pwmout_t *obj, int ms) +{ +} + +void pwmout_period_us(pwmout_t *obj, int us) +{ +} + +void pwmout_pulsewidth(pwmout_t *obj, float seconds) +{ +} + +void pwmout_pulsewidth_ms(pwmout_t *obj, int ms) +{ +} + +void pwmout_pulsewidth_us(pwmout_t *obj, int us) +{ +} + +#endif // DEVICE_PWMOUT diff --git a/UNITTESTS/target_h/PinNames.h b/UNITTESTS/target_h/PinNames.h index 243c64fd14..f580d15ae6 100644 --- a/UNITTESTS/target_h/PinNames.h +++ b/UNITTESTS/target_h/PinNames.h @@ -34,6 +34,10 @@ typedef enum { } PinName; typedef enum { + PullNone = 0, + PullDown = 1, + PullUp = 2, + PullDefault = PullUp } PinMode; #ifdef __cplusplus diff --git a/UNITTESTS/target_h/objects.h b/UNITTESTS/target_h/objects.h index 5753bbdcd4..13f95e1992 100644 --- a/UNITTESTS/target_h/objects.h +++ b/UNITTESTS/target_h/objects.h @@ -18,6 +18,7 @@ #ifndef MBED_OBJECTS_H #define MBED_OBJECTS_H +#include #include "PeripheralNames.h" #include "PinNames.h" @@ -33,6 +34,12 @@ struct serial_s { int x; }; +struct pwmout_s { + int pwm_name; +}; + + + #include "gpio_object.h" #ifdef __cplusplus diff --git a/UNITTESTS/target_h/platform/mbed_power_mgmt.h b/UNITTESTS/target_h/platform/mbed_power_mgmt.h index d0475a10d7..5ebb18c095 100644 --- a/UNITTESTS/target_h/platform/mbed_power_mgmt.h +++ b/UNITTESTS/target_h/platform/mbed_power_mgmt.h @@ -21,6 +21,7 @@ */ #ifndef MBED_POWER_MGMT_H #define MBED_POWER_MGMT_H + extern void mock_system_reset(); MBED_NORETURN static inline void system_reset(void) @@ -28,5 +29,9 @@ MBED_NORETURN static inline void system_reset(void) mock_system_reset(); } +void sleep_manager_unlock_deep_sleep(void); + +void sleep_manager_lock_deep_sleep(void); + #endif diff --git a/drivers/source/PwmOut.cpp b/drivers/source/PwmOut.cpp index 2951bbe414..ef0c8821a8 100644 --- a/drivers/source/PwmOut.cpp +++ b/drivers/source/PwmOut.cpp @@ -37,7 +37,6 @@ PwmOut::PwmOut(PinName pin) : PwmOut::~PwmOut() { - MBED_ASSERT(!_initialized); PwmOut::deinit(); }