tests: drivers: pwm: add PWM loopback test

Add test cases for the PWM capture API using PWM signal loopback.

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
This commit is contained in:
Henrik Brix Andersen 2020-12-19 16:07:02 +01:00 committed by Carles Cufí
commit 90825a8812
8 changed files with 501 additions and 0 deletions

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(pwm_loopback)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2020-2021 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <dt-bindings/pwm/pwm.h>
/ {
pwm_loopback_0 {
compatible = "test,pwm_loopback";
pwms = <&ftm0 0 0 PWM_POLARITY_NORMAL>, /* PTC1, J1 pin 5 */
<&ftm3 4 0 PWM_POLARITY_NORMAL>; /* PTC8, J1 pin 7 */
};
};

View file

@ -0,0 +1,22 @@
#
# Copyright (c) 2020-2021 Vestas Wind Systems A/S
#
# SPDX-License-Identifier: Apache-2.0
#
description: |
This binding provides resources required to build and run the
tests/drivers/pwm/pwm_loopback test in Zephyr.
compatible: "test,pwm_loopback"
properties:
pwms:
type: phandle-array
required: true
description: |
PWM pins that will be used for generating and capturing a pulse-width
modulated signal. The pin at the first index will be used for signal
generation while the pin at the second index will be used for capuring
the generated signal. The two pins must be physically connected to
each other.

View file

@ -0,0 +1,5 @@
CONFIG_ZTEST=y
CONFIG_TEST_USERSPACE=y
CONFIG_PWM=y
CONFIG_PWM_CAPTURE=y

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020-2021 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <ztest.h>
#include "test_pwm_loopback.h"
void test_main(void)
{
struct test_pwm in;
struct test_pwm out;
get_test_pwms(&in, &out);
k_object_access_grant(out.dev, k_current_get());
k_object_access_grant(in.dev, k_current_get());
ztest_test_suite(pwm_loopback_test,
ztest_user_unit_test(test_pulse_capture),
ztest_user_unit_test(test_pulse_capture_inverted),
ztest_user_unit_test(test_period_capture),
ztest_user_unit_test(test_period_capture_inverted),
ztest_user_unit_test(test_pulse_and_period_capture),
ztest_user_unit_test(test_capture_timeout),
ztest_unit_test(test_continuous_capture),
ztest_unit_test(test_capture_busy));
ztest_run_test_suite(pwm_loopback_test);
}

View file

@ -0,0 +1,344 @@
/*
* Copyright (c) 2020-2021 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <drivers/pwm.h>
#include <ztest.h>
#include "test_pwm_loopback.h"
#define TEST_PWM_PERIOD_NSEC 100000000
#define TEST_PWM_PULSE_NSEC 15000000
#define TEST_PWM_PERIOD_USEC 100000
#define TEST_PWM_PULSE_USEC 75000
enum test_pwm_unit {
TEST_PWM_UNIT_NSEC,
TEST_PWM_UNIT_USEC,
};
void get_test_pwms(struct test_pwm *out, struct test_pwm *in)
{
/* PWM generator device */
out->dev = device_get_binding(PWM_LOOPBACK_OUT_LABEL);
out->pwm = PWM_LOOPBACK_OUT_CHANNEL;
out->flags = PWM_LOOPBACK_OUT_FLAGS;
zassert_not_null(out->dev, "pwm loopback output device not found");
/* PWM capture device */
in->dev = device_get_binding(PWM_LOOPBACK_IN_LABEL);
in->pwm = PWM_LOOPBACK_IN_CHANNEL;
in->flags = PWM_LOOPBACK_IN_FLAGS;
zassert_not_null(in->dev, "pwm loopback input device not found");
}
void test_capture(uint32_t period, uint32_t pulse, enum test_pwm_unit unit,
pwm_flags_t flags)
{
struct test_pwm in;
struct test_pwm out;
uint64_t period_capture = 0;
uint64_t pulse_capture = 0;
int err = 0;
get_test_pwms(&out, &in);
switch (unit) {
case TEST_PWM_UNIT_NSEC:
TC_PRINT("Testing PWM capture @ %u/%u nsec\n",
pulse, period);
err = pwm_pin_set_nsec(out.dev, out.pwm, period,
pulse, out.flags);
break;
case TEST_PWM_UNIT_USEC:
TC_PRINT("Testing PWM capture @ %u/%u usec\n",
pulse, period);
err = pwm_pin_set_usec(out.dev, out.pwm, period,
pulse, out.flags);
break;
default:
TC_PRINT("Unsupported test unit");
ztest_test_fail();
}
zassert_equal(err, 0, "failed to set pwm output (err %d)", err);
switch (unit) {
case TEST_PWM_UNIT_NSEC:
err = pwm_pin_capture_nsec(in.dev, in.pwm, flags,
&period_capture, &pulse_capture,
K_NSEC(period * 10));
break;
case TEST_PWM_UNIT_USEC:
err = pwm_pin_capture_usec(in.dev, in.pwm, flags,
&period_capture, &pulse_capture,
K_USEC(period * 10));
break;
default:
TC_PRINT("Unsupported test unit");
ztest_test_fail();
}
if (err == -ENOTSUP) {
TC_PRINT("capture type not supported\n");
ztest_test_skip();
}
zassert_equal(err, 0, "failed to capture pwm (err %d)", err);
if (flags & PWM_CAPTURE_TYPE_PERIOD) {
zassert_within(period_capture, period, period / 100,
"period capture off by more than 1%");
}
if (flags & PWM_CAPTURE_TYPE_PULSE) {
if (flags & PWM_POLARITY_INVERTED) {
zassert_within(pulse_capture, period - pulse,
(period - pulse) / 100,
"pulse capture off by more than 1%");
} else {
zassert_within(pulse_capture, pulse, pulse / 100,
"pulse capture off by more than 1%");
}
}
}
void test_pulse_capture(void)
{
test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC,
TEST_PWM_UNIT_NSEC,
PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_NORMAL);
test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC,
TEST_PWM_UNIT_USEC,
PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_NORMAL);
}
void test_pulse_capture_inverted(void)
{
test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC,
TEST_PWM_UNIT_NSEC,
PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_INVERTED);
test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC,
TEST_PWM_UNIT_USEC,
PWM_CAPTURE_TYPE_PULSE | PWM_POLARITY_INVERTED);
}
void test_period_capture(void)
{
test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC,
TEST_PWM_UNIT_NSEC,
PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_NORMAL);
test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC,
TEST_PWM_UNIT_USEC,
PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_NORMAL);
}
void test_period_capture_inverted(void)
{
test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC,
TEST_PWM_UNIT_NSEC,
PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_INVERTED);
test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC,
TEST_PWM_UNIT_USEC,
PWM_CAPTURE_TYPE_PERIOD | PWM_POLARITY_INVERTED);
}
void test_pulse_and_period_capture(void)
{
test_capture(TEST_PWM_PERIOD_NSEC, TEST_PWM_PULSE_NSEC,
TEST_PWM_UNIT_NSEC,
PWM_CAPTURE_TYPE_BOTH | PWM_POLARITY_NORMAL);
test_capture(TEST_PWM_PERIOD_USEC, TEST_PWM_PULSE_USEC,
TEST_PWM_UNIT_USEC,
PWM_CAPTURE_TYPE_BOTH | PWM_POLARITY_NORMAL);
}
void test_capture_timeout(void)
{
struct test_pwm in;
struct test_pwm out;
uint32_t period;
uint32_t pulse;
int err;
get_test_pwms(&out, &in);
err = pwm_pin_set_cycles(out.dev, out.pwm, 100, 0, out.flags);
zassert_equal(err, 0, "failed to set pwm output (err %d)", err);
err = pwm_pin_capture_cycles(in.dev, in.pwm,
PWM_CAPTURE_TYPE_PULSE,
&period, &pulse, K_MSEC(1000));
if (err == -ENOTSUP) {
TC_PRINT("Pulse capture not supported, "
"trying period capture\n");
err = pwm_pin_capture_cycles(in.dev, in.pwm,
PWM_CAPTURE_TYPE_PERIOD,
&period, &pulse, K_MSEC(1000));
}
zassert_equal(err, -EAGAIN, "pwm capture did not timeout (err %d)",
err);
}
static void continuous_capture_callback(const struct device *dev,
uint32_t pwm,
uint32_t period_cycles,
uint32_t pulse_cycles,
int status,
void *user_data)
{
struct test_pwm_callback_data *data = user_data;
if (data->count > data->buffer_len) {
/* Safe guard in case capture is not disabled */
return;
}
if (status != 0) {
/* Error occurred */
data->status = status;
k_sem_give(&data->sem);
}
if (data->pulse_capture) {
data->buffer[data->count++] = pulse_cycles;
} else {
data->buffer[data->count++] = period_cycles;
}
if (data->count > data->buffer_len) {
data->status = 0;
k_sem_give(&data->sem);
}
}
void test_continuous_capture(void)
{
static const uint32_t period_usec = 10000;
static const uint32_t pulse_usec = 7500;
struct test_pwm in;
struct test_pwm out;
uint32_t buffer[10];
struct test_pwm_callback_data data = {
.buffer = buffer,
.buffer_len = ARRAY_SIZE(buffer),
.count = 0,
.pulse_capture = true,
};
uint64_t usec = 0;
int err;
int i;
get_test_pwms(&out, &in);
memset(buffer, 0, sizeof(buffer));
k_sem_init(&data.sem, 0, 1);
err = pwm_pin_set_usec(out.dev, out.pwm, period_usec, pulse_usec,
out.flags);
zassert_equal(err, 0, "failed to set pwm output (err %d)", err);
err = pwm_pin_configure_capture(in.dev, in.pwm,
in.flags |
PWM_CAPTURE_MODE_CONTINUOUS |
PWM_CAPTURE_TYPE_PULSE,
continuous_capture_callback,
&data);
if (err == -ENOTSUP) {
TC_PRINT("Pulse capture not supported, "
"trying period capture\n");
err = pwm_pin_configure_capture(in.dev, in.pwm,
in.flags |
PWM_CAPTURE_MODE_CONTINUOUS |
PWM_CAPTURE_TYPE_PERIOD,
continuous_capture_callback,
&data);
zassert_equal(err, 0, "failed to configure pwm input (err %d)",
err);
data.pulse_capture = false;
}
err = pwm_pin_enable_capture(in.dev, in.pwm);
zassert_equal(err, 0, "failed to enable pwm capture (err %d)", err);
err = k_sem_take(&data.sem, K_USEC(period_usec * data.buffer_len * 10));
zassert_equal(err, 0, "pwm capture timed out (err %d)", err);
zassert_equal(data.status, 0, "pwm capture failed (err %d)", err);
err = pwm_pin_disable_capture(in.dev, in.pwm);
zassert_equal(err, 0, "failed to disable pwm capture (err %d)", err);
for (i = 0; i < data.buffer_len; i++) {
err = pwm_pin_cycles_to_usec(in.dev, in.pwm, buffer[i], &usec);
zassert_equal(err, 0, "failed to calculate usec (err %d)", err);
if (data.pulse_capture) {
zassert_within(usec, pulse_usec, pulse_usec / 100,
"pulse capture off by more than 1%");
} else {
zassert_within(usec, period_usec, period_usec / 100,
"period capture off by more than 1%");
}
}
}
void test_capture_busy(void)
{
struct test_pwm in;
struct test_pwm out;
uint32_t buffer[10];
struct test_pwm_callback_data data = {
.buffer = buffer,
.buffer_len = ARRAY_SIZE(buffer),
.count = 0,
.pulse_capture = true,
};
pwm_flags_t flags = PWM_CAPTURE_MODE_SINGLE |
PWM_CAPTURE_TYPE_PULSE;
int err;
get_test_pwms(&out, &in);
memset(buffer, 0, sizeof(buffer));
k_sem_init(&data.sem, 0, 1);
err = pwm_pin_set_cycles(out.dev, out.pwm, 100, 0, out.flags);
zassert_equal(err, 0, "failed to set pwm output (err %d)", err);
err = pwm_pin_configure_capture(in.dev, in.pwm,
in.flags | flags,
continuous_capture_callback,
&data);
if (err == -ENOTSUP) {
TC_PRINT("Pulse capture not supported, "
"trying period capture\n");
flags = PWM_CAPTURE_MODE_SINGLE | PWM_CAPTURE_TYPE_PERIOD;
err = pwm_pin_configure_capture(in.dev, in.pwm,
in.flags | flags,
continuous_capture_callback,
&data);
zassert_equal(err, 0, "failed to configure pwm input (err %d)",
err);
data.pulse_capture = false;
}
err = pwm_pin_enable_capture(in.dev, in.pwm);
zassert_equal(err, 0, "failed to enable pwm capture (err %d)", err);
err = pwm_pin_configure_capture(in.dev, in.pwm,
in.flags | flags,
continuous_capture_callback,
&data);
zassert_equal(err, -EBUSY, "pwm capture not busy (err %d)", err);
err = pwm_pin_disable_capture(in.dev, in.pwm);
zassert_equal(err, 0, "failed to disable pwm capture (err %d)", err);
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020-2021 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __TEST_PWM_LOOPBACK_H__
#define __TEST_PWM_LOOPBACK_H__
#include <zephyr.h>
#include <drivers/pwm.h>
#include <ztest.h>
#define PWM_LOOPBACK_OUT_IDX 0
#define PWM_LOOPBACK_IN_IDX 1
#define PWM_LOOPBACK_NODE DT_INST(0, test_pwm_loopback)
#define PWM_LOOPBACK_OUT_LABEL \
DT_PWMS_LABEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX)
#define PWM_LOOPBACK_OUT_CHANNEL \
DT_PWMS_CHANNEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX)
#define PWM_LOOPBACK_OUT_FLAGS \
DT_PWMS_FLAGS_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_OUT_IDX)
#define PWM_LOOPBACK_IN_LABEL \
DT_PWMS_LABEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX)
#define PWM_LOOPBACK_IN_CHANNEL \
DT_PWMS_CHANNEL_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX)
#define PWM_LOOPBACK_IN_FLAGS \
DT_PWMS_FLAGS_BY_IDX(PWM_LOOPBACK_NODE, PWM_LOOPBACK_IN_IDX)
struct test_pwm {
const struct device *dev;
uint32_t pwm;
pwm_flags_t flags;
};
struct test_pwm_callback_data {
uint32_t *buffer;
size_t buffer_len;
size_t count;
int status;
struct k_sem sem;
bool pulse_capture;
};
void get_test_pwms(struct test_pwm *out, struct test_pwm *in);
void test_pulse_capture(void);
void test_pulse_capture_inverted(void);
void test_period_capture(void);
void test_period_capture_inverted(void);
void test_pulse_and_period_capture(void);
void test_capture_timeout(void);
void test_continuous_capture(void);
void test_capture_busy(void);
#endif /* __TEST_PWM_LOOPBACK_H__ */

View file

@ -0,0 +1,8 @@
tests:
drivers.pwm.loopback:
tags: pwm drivers userspace
depends_on: pwm
filter: dt_compat_enabled("test,pwm_loopback")
harness: ztest
harness_config:
fixture: pwm_loopback