Newer
Older
mbed-os / drivers / tests / TESTS / mbed_drivers / ticker / main.cpp
/* mbed Microcontroller Library
 * Copyright (c) 2013-2017 ARM Limited
 * 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "utest/utest.h"
#include "unity/unity.h"

#if !DEVICE_USTICKER
#error [NOT_SUPPORTED] test not supported
#else

using utest::v1::Case;

using namespace std::chrono;

#define TEST_ASSERT_EQUAL_DURATION(expected, actual) \
    do { \
        using ct = std::common_type_t<decltype(expected), decltype(actual)>; \
        TEST_ASSERT_EQUAL(ct(expected).count(), ct(actual).count()); \
    } while (0)

#define TEST_ASSERT_DURATION_WITHIN(delta, expected, actual) \
    do { \
        using ct = std::common_type_t<decltype(delta), decltype(expected), decltype(actual)>; \
        TEST_ASSERT_INT_WITHIN(ct(delta).count(), ct(expected).count(), ct(actual).count()); \
    } while (0)

#define TICKER_TIME  1ms
#define DOUBLE_TICKER_TIME 2ms
#define MULTI_TICKER_TIME 100ms

#define TICKER_COUNT 16
volatile uint32_t callback_trigger_count = 0;
static const int test_timeout = 240;


/* Tolerance is quite arbitrary due to large number of boards with varying level of accuracy */
#define TOLERANCE 1000us

volatile uint32_t ticker_callback_flag;
volatile uint32_t multi_counter;

Timer gtimer;
volatile int ticker_count = 0;

void ticker_callback_1(void)
{
    ++callback_trigger_count;
}

void ticker_callback_2(void)
{
    ++callback_trigger_count;
}


void sem_release(Semaphore *sem)
{
    sem->release();
}


void stop_gtimer_set_flag(void)
{
    gtimer.stop();
    core_util_atomic_incr_u32((uint32_t *)&ticker_callback_flag, 1);
}

void increment_multi_counter(void)
{
    core_util_atomic_incr_u32((uint32_t *)&multi_counter, 1);
}


/* Tests is to measure the accuracy of Ticker over a period of time
 *
 * 1) DUT would start to update callback_trigger_count every milli sec
 * 2) Host would query what is current count base_time, Device responds by the callback_trigger_count.
 * 3) Host after waiting for measurement stretch. It will query for device time again final_time.
 * 4) Host computes the drift considering base_time, final_time, transport delay and measurement stretch
 * 5) Finally host send the results back to device pass/fail based on tolerance.
 * 6) More details on tests can be found in timing_drift_auto.py
 */
void test_case_1x_ticker()
{
    char _key[11] = { };
    char _value[128] = { };
    int expected_key = 1;
    Ticker ticker;

    callback_trigger_count = 0;

    greentea_send_kv("timing_drift_check_start", 0);
    ticker.attach(&ticker_callback_1, TICKER_TIME);

    // wait for 1st signal from host
    do {
        greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
        expected_key = strcmp(_key, "base_time");
    } while (expected_key);
    greentea_send_kv(_key, callback_trigger_count * microseconds{TICKER_TIME}.count());

    // wait for 2nd signal from host
    greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
    greentea_send_kv(_key, callback_trigger_count * microseconds{TICKER_TIME}.count());

    //get the results from host
    greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));

    TEST_ASSERT_EQUAL_STRING_MESSAGE("pass", _key, "Host side script reported a fail...");
}

/* Tests is to measure the accuracy of Ticker over a period of time
 *
 * 1) DUT would start to update callback_trigger_count every milli sec, we use 2 tickers
 *    to update the count alternatively.
 * 2) Host would query what is current count base_time, Device responds by the callback_trigger_count
 * 3) Host after waiting for measurement stretch. It will query for device time again final_time.
 * 4) Host computes the drift considering base_time, final_time, transport delay and measurement stretch
 * 5) Finally host send the results back to device pass/fail based on tolerance.
 * 6) More details on tests can be found in timing_drift_auto.py
 */
void test_case_2x_ticker()
{
    char _key[11] = { };
    char _value[128] = { };
    int expected_key =  1;
    Ticker ticker1, ticker2;

    callback_trigger_count = 0;

    ticker1.attach(ticker_callback_1, DOUBLE_TICKER_TIME);
    // delay second ticker to have a pair of tickers tick every one millisecond
    ThisThread::sleep_for(TICKER_TIME);
    greentea_send_kv("timing_drift_check_start", 0);
    ticker2.attach(ticker_callback_2, DOUBLE_TICKER_TIME);

    // wait for 1st signal from host
    do {
        greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
        expected_key = strcmp(_key, "base_time");
    } while (expected_key);
    greentea_send_kv(_key, callback_trigger_count * microseconds{TICKER_TIME}.count());

    // wait for 2nd signal from host
    greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
    greentea_send_kv(_key, callback_trigger_count * microseconds{TICKER_TIME}.count());

    //get the results from host
    greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));

    TEST_ASSERT_EQUAL_STRING_MESSAGE("pass", _key, "Host side script reported a fail...");
}

/** Test many tickers run one after the other

    Given many Tickers
    When schedule them one after the other with the same time intervals
    Then tickers properly execute callbacks
    When schedule them one after the other with the different time intervals
    Then tickers properly execute callbacks
 */
void test_multi_ticker(void)
{
    Ticker ticker[TICKER_COUNT];
    const milliseconds extra_wait = 5ms; // extra 5ms wait time

    multi_counter = 0;
    for (int i = 0; i < TICKER_COUNT; i++) {
        ticker[i].attach(callback(increment_multi_counter), MULTI_TICKER_TIME);
    }

    ThisThread::sleep_for(MULTI_TICKER_TIME + extra_wait);

    TEST_ASSERT_EQUAL(TICKER_COUNT, multi_counter);

    for (int i = 0; i < TICKER_COUNT; i++) {
        ticker[i].detach();
    }
    // Because detach calls schedule_interrupt in some circumstances
    // (e.g. when head event is removed), it's good to check if
    // no more callbacks were triggered during detaching.
    TEST_ASSERT_EQUAL(TICKER_COUNT, multi_counter);

    multi_counter = 0;
    for (int i = 0; i < TICKER_COUNT; i++) {
        ticker[i].attach(callback(increment_multi_counter), MULTI_TICKER_TIME + milliseconds{i});
    }

    ThisThread::sleep_for(MULTI_TICKER_TIME + milliseconds{TICKER_COUNT} + extra_wait);
    TEST_ASSERT_EQUAL(TICKER_COUNT, multi_counter);

    for (int i = 0; i < TICKER_COUNT; i++) {
        ticker[i].detach();
    }
    // Because detach calls schedule_interrupt in some circumstances
    // (e.g. when head event is removed), it's good to check if
    // no more callbacks were triggered during detaching.
    TEST_ASSERT_EQUAL(TICKER_COUNT, multi_counter);
}

/** Test multi callback time

    Given a Ticker
    When the callback is attached multiple times
    Then ticker properly execute callback multiple times
 */
void test_multi_call_time(void)
{
    Ticker ticker;
    const int attach_count = 10;

    for (int i = 0; i < attach_count; i++) {
        ticker_callback_flag = 0;
        gtimer.reset();

        gtimer.start();
        ticker.attach(callback(stop_gtimer_set_flag), MULTI_TICKER_TIME);
        while (!ticker_callback_flag);
        const auto time_diff = gtimer.elapsed_time();

        TEST_ASSERT_DURATION_WITHIN(TOLERANCE, MULTI_TICKER_TIME, time_diff);
    }
}

/** Test if detach cancel scheduled callback event

    Given a Ticker with callback attached
    When the callback is detached
    Then the callback is not being called
 */
void test_detach(void)
{
    Ticker ticker;
    const milliseconds ticker_time = 100ms;
    const milliseconds wait_time = 500ms;
    Semaphore sem(0, 1);

    ticker.attach(callback(sem_release, &sem), ticker_time);

    sem.acquire();

    sem.acquire();
    ticker.detach(); /* cancel */

    TEST_ASSERT_FALSE(sem.try_acquire_for(wait_time));
}

/** Test single callback time via attach

    Given a Ticker
    When callback attached with time interval specified
    Then ticker properly executes callback within a specified time interval
 */
template<typename Duration, uint64_t DELAY>
void test_attach_time(void)
{
    Ticker ticker;
    ticker_callback_flag = 0;
    const auto delay = Duration{DELAY};

    gtimer.reset();
    gtimer.start();
    ticker.attach(callback(stop_gtimer_set_flag), delay);
    while (!ticker_callback_flag);
    ticker.detach();
    const auto time_diff = gtimer.elapsed_time();

    TEST_ASSERT_DURATION_WITHIN(TOLERANCE, delay, time_diff);
}


// Test cases
Case cases[] = {
    Case("Test attach for 10ms and time measure", test_attach_time<milliseconds, 10>),
    Case("Test attach for 100ms and time measure", test_attach_time<milliseconds, 100>),
    Case("Test attach for 500ms and time measure", test_attach_time<milliseconds, 500>),
    Case("Test detach", test_detach),
    Case("Test multi call and time measure", test_multi_call_time),
    Case("Test multi ticker", test_multi_ticker),

#if !defined(SKIP_TIME_DRIFT_TESTS)
    Case("Test timers: 1x ticker", test_case_1x_ticker),
    Case("Test timers: 2x ticker", test_case_2x_ticker)
#endif
};

utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
    GREENTEA_SETUP(test_timeout, "timing_drift_auto");
    return utest::v1::greentea_test_setup_handler(number_of_cases);
}

utest::v1::Specification specification(greentea_test_setup, cases, utest::v1::greentea_test_teardown_handler);

int main()
{
    utest::v1::Harness::run(specification);
}

#endif // !DEVICE_USTICKER