mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
d7cf39551f
This test application makes use of the RIOT Peripheral Selftest Shield, which connects e.g. PWM to ADC or SPI MOSI to SPI MISO, UART TXD to RXD, etc. This provides quick and fully automated self testing capabilities. Please note that the simplicity and ease of use of the hardware comes with a prices: There are whole classes of issues that cannot be detected automatically. This test cannot replace other testing approaches (such as manual testing or PHiLIP on the HiL), but only complement them.
1047 lines
30 KiB
C
1047 lines
30 KiB
C
/*
|
|
* Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup tests
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Test application for the Peripheral GPIO Low-Level API
|
|
*
|
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "arduino_iomap.h"
|
|
#include "macros/units.h"
|
|
#include "macros/utils.h"
|
|
#include "modules.h"
|
|
#include "pcf857x.h"
|
|
#include "periph/adc.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph/pwm.h"
|
|
#include "periph/spi.h"
|
|
#include "periph/timer.h"
|
|
#include "periph/uart.h"
|
|
#include "stdio_uart.h" /* for STDIO_UART_DEV */
|
|
|
|
/* BEGIN: controls of the behavior of the testing app: */
|
|
#define ENABLE_DEBUG 1
|
|
#include "debug.h"
|
|
|
|
#ifndef STOP_ON_FAILURE
|
|
#define STOP_ON_FAILURE 0
|
|
#endif
|
|
|
|
#ifndef DETAILED_OUTPUT
|
|
#define DETAILED_OUTPUT 0
|
|
#endif
|
|
/* END: controls of the behavior of the testing app: */
|
|
|
|
/* In order to run the periph_uart test all of the following needs to be true:
|
|
* - periph_uart needs to be used
|
|
* - an I/O mapping for the UART at D0/D1 needs to be provided
|
|
* - this UART dev is not busy with stdio
|
|
*/
|
|
#if defined(ARDUINO_UART_D0D1) && defined(MODULE_PERIPH_UART)
|
|
# define ENABLE_UART_TEST (STDIO_UART_DEV != ARDUINO_UART_D0D1)
|
|
# define UART_TEST_DEV ARDUINO_UART_D0D1
|
|
#else
|
|
# define ENABLE_UART_TEST 0
|
|
#endif
|
|
|
|
/* In order to run the periph_pwm test, all of the following needs to be true:
|
|
* - periph_pwm needs to be used (so that we actually have something to test)
|
|
* - periph_adc needs to be used (so that we can validate the output duty cycle)
|
|
* - At least one of:
|
|
* - Arduino I/O mapping for PWM on D5 *AND* analog pin A2 is provided
|
|
* - Arduino I/O mapping for PWM on D6 *AND* analog pin A1 is provided
|
|
*/
|
|
#if defined(MODULE_PERIPH_PWM) && defined(MODULE_PERIPH_ADC)
|
|
# if defined(ARDUINO_PIN_5_PWM_DEV) && defined(ARDUINO_A2)
|
|
# define ENABLE_PWM_TEST_D5 1
|
|
# else
|
|
# define ENABLE_PWM_TEST_D5 0
|
|
# endif
|
|
# if defined(ARDUINO_PIN_6_PWM_DEV) && defined(ARDUINO_A1)
|
|
# define ENABLE_PWM_TEST_D6 1
|
|
# else
|
|
# define ENABLE_PWM_TEST_D6 0
|
|
# endif
|
|
# define ENABLE_PWM_TEST (ENABLE_PWM_TEST_D5 || ENABLE_PWM_TEST_D6)
|
|
#else
|
|
# define ENABLE_PWM_TEST 0
|
|
# define ENABLE_PWM_TEST_D5 0
|
|
# define ENABLE_PWM_TEST_D6 0
|
|
#endif
|
|
|
|
/* In order to run the periph_adc test, we need:
|
|
* - periph_adc support (so that we have something to test)
|
|
* - Arduino I/O mapping for ADC (so that we know which line to test)
|
|
* - The PCF857x driver (so that we can control the R-2R resistor ladder
|
|
* connected to the GPIO expander.
|
|
*/
|
|
#if defined(MODULE_PERIPH_ADC) && defined(ARDUINO_A0) && defined(MODULE_PCF857X) && 0
|
|
// TODO: Re-anble when PCB is fixed
|
|
# define ENABLE_ADC_TEST 1
|
|
#else
|
|
# define ENABLE_ADC_TEST 0
|
|
#endif
|
|
|
|
/* We want the code to be compile-tested even when tests are disabled. We
|
|
* provide dummy parameters for the tests when they are off. The actual
|
|
* values don't matter, as the tests will be disabled anyway when the
|
|
* parameters are missing. But having the compiler checking the syntax and
|
|
* doing static analysis is useful in any case. */
|
|
#ifndef ARDUINO_PIN_5_PWM_DEV
|
|
# define ARDUINO_PIN_5_PWM_DEV -1
|
|
# define ARDUINO_PIN_5_PWM_CHAN -1
|
|
#endif
|
|
#ifndef ARDUINO_PIN_6_PWM_DEV
|
|
# define ARDUINO_PIN_6_PWM_DEV -1
|
|
# define ARDUINO_PIN_6_PWM_CHAN -1
|
|
#endif
|
|
#ifndef ARDUINO_A0
|
|
# define ARDUINO_A0 -1
|
|
#endif
|
|
#ifndef ARDUINO_A1
|
|
# define ARDUINO_A1 -1
|
|
#endif
|
|
#ifndef ARDUINO_A2
|
|
# define ARDUINO_A2 -1
|
|
#endif
|
|
#ifndef UART_TEST_DEV
|
|
# define UART_TEST_DEV UART_DEV(0)
|
|
#endif
|
|
#ifndef TIMER
|
|
# define TIMER TIMER_DEV(0)
|
|
#endif
|
|
|
|
/* A higher clock frequency is beneficial in being able to actually measure the
|
|
* difference of half a SPI clock cycle when clock phase is 1, compared to
|
|
* clock phase being 0. Most MCUs can clock their timers from the core clock
|
|
* directly, so the CPU clock is the highest clock frequency available. But
|
|
* some can't, so we handle them here explicitly. */
|
|
#ifndef TIMER_FREQ_SPI_TEST
|
|
# if defined(CPU_SAM3)
|
|
# define TIMER_FREQ_SPI_TEST CLOCK_CORECLOCK / 4
|
|
# elif defined(CPU_NRF52) || defined(CPU_NRF51)
|
|
# define TIMER_FREQ_SPI_TEST MHZ(16)
|
|
# else
|
|
# define TIMER_FREQ_SPI_TEST CLOCK_CORECLOCK
|
|
# endif
|
|
#endif
|
|
|
|
/* for the UART test a slower frequency is more beneficial. We assume the
|
|
* timer to be 16 bit (tossing away the upper 16 bit of 32 bit timers) to
|
|
* ease the test. But the duration for transferring 8 bytes of UART data in
|
|
* ticks easily overflows 16 bit at higher frequencies, so we just go for
|
|
* a lower frequency instead. */
|
|
#ifndef TIMER_FREQ_UART_TEST
|
|
# if defined(__AVR__)
|
|
# define TIMER_FREQ_UART_TEST CLOCK_CORECLOCK / 64
|
|
# else
|
|
# define TIMER_FREQ_UART_TEST MHZ(1)
|
|
# endif
|
|
#endif
|
|
|
|
static const char testdata[8] = "Selftest";
|
|
static const spi_t spi_buses[] = {
|
|
#ifdef ARDUINO_SPI_D11D12D13
|
|
ARDUINO_SPI_D11D12D13,
|
|
#endif
|
|
#ifdef ARDUINO_SPI_ISP
|
|
ARDUINO_SPI_ISP,
|
|
#endif
|
|
};
|
|
static const gpio_t spi_clk_check_pins[] = {
|
|
#ifdef ARDUINO_SPI_D11D12D13
|
|
PCF857X_GPIO_PIN(0, 2),
|
|
#endif
|
|
#ifdef ARDUINO_SPI_ISP
|
|
PCF857X_GPIO_PIN(0, 3),
|
|
#endif
|
|
};
|
|
|
|
static struct {
|
|
char data[8];
|
|
uint8_t pos;
|
|
} serial_buf;
|
|
|
|
/* This module is only used when both periph_i2c and the Arduino I/O
|
|
* mapping are present. If this module is not used, this test is optimized
|
|
* out as dead branch. We still want the compile test, though. */
|
|
static const pcf857x_params_t params = {
|
|
#ifdef MODULE_PCF857X
|
|
.dev = ARDUINO_I2C_UNO,
|
|
.exp = PCF857X_EXP_PCF8574,
|
|
#endif
|
|
};
|
|
|
|
static pcf857x_t egpios;
|
|
|
|
/* Some tests may pass due to luck (e.g. when expecting a pull up but input
|
|
* is floating, it will still get lucky from time to time). We repeat those
|
|
* in a loop to have some confidence that the test is consistently passing */
|
|
static const unsigned flaky_test_repetitions = 100;
|
|
|
|
/* We are trying to not spent much memory on error message for compatibility
|
|
* with as many boards as possible. The idea is that the line number where a
|
|
* test failed and careful commenting in the code provides the same level of
|
|
* insight with only a little inconvenience of having to open the editor. */
|
|
static void print_test_failed(uint16_t line)
|
|
{
|
|
printf("FAILURE in " __FILE__ ":%" PRIu16 "\n", line);
|
|
}
|
|
|
|
static bool do_test(bool failed, uint16_t line)
|
|
{
|
|
if (failed) {
|
|
print_test_failed(line);
|
|
if (STOP_ON_FAILURE) {
|
|
printf("Stopping, as STOP_ON_FAILURE==1\n");
|
|
ARCHITECTURE_BREAKPOINT(1);
|
|
while (1) {
|
|
/* stop */
|
|
}
|
|
}
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static void do_assert(bool failed, uint16_t line)
|
|
{
|
|
if (failed) {
|
|
printf("CRITICAL ");
|
|
print_test_failed(line);
|
|
ARCHITECTURE_BREAKPOINT(1);
|
|
while (1) {
|
|
/* stop */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_result(bool failed)
|
|
{
|
|
if (failed) {
|
|
printf("[FAILED]\n");
|
|
}
|
|
else {
|
|
printf("[OK]\n");
|
|
}
|
|
}
|
|
|
|
static void print_skipped(void)
|
|
{
|
|
printf("(skipped)\n");
|
|
}
|
|
|
|
static void _print_start(const char *name, const char *detail, uint16_t line)
|
|
{
|
|
if (DETAILED_OUTPUT) {
|
|
printf("Starting test for %s (%s) at " __FILE__ ":%" PRIu16"\n", name,
|
|
detail, line);
|
|
}
|
|
else {
|
|
printf("Starting test for %s at " __FILE__ ":%" PRIu16"\n", name,
|
|
line);
|
|
}
|
|
}
|
|
|
|
#if DETAILED_OUTPUT
|
|
# define print_start(name, detail) _print_start(name, detail, __LINE__)
|
|
#else
|
|
# define print_start(name, detail) _print_start(name, NULL, __LINE__)
|
|
#endif
|
|
|
|
#define TEST(x) do_test(!(x), __LINE__)
|
|
|
|
#define ASSERT(x) do_assert(!(x), __LINE__)
|
|
|
|
static void stupid_delay(unsigned count)
|
|
{
|
|
while (count > 0) {
|
|
/* tell optimizer that the value of `count` is used and changed, so
|
|
* that the down-counting loop is not detected as dead code */
|
|
__asm__ volatile (
|
|
""
|
|
/* outputs: */
|
|
: "+r"(count)
|
|
/* inputs: */
|
|
:
|
|
/* clobbers: */
|
|
);
|
|
count--;
|
|
}
|
|
}
|
|
|
|
static void brief_delay(void)
|
|
{
|
|
stupid_delay(100);
|
|
}
|
|
|
|
static void long_delay(void)
|
|
{
|
|
stupid_delay(10000);
|
|
}
|
|
|
|
static bool periph_gpio_test_push_pull(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO", "push-pull");
|
|
ASSERT(gpio_init(ARDUINO_PIN_3, GPIO_IN) == 0);
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) == 0);
|
|
}
|
|
|
|
gpio_set(ARDUINO_PIN_4);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) != 0);
|
|
}
|
|
|
|
print_result(failed);
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_test_input_pull_up(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO", "input pull-up");
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_IN) == 0);
|
|
if (gpio_init(ARDUINO_PIN_3, GPIO_IN_PU) == 0) {
|
|
/* give pull resistor a little time to pull */
|
|
brief_delay();
|
|
/* pull up should pull both D3 and D4 up, as D4 is connected to D3 via
|
|
* the testing shield */
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) != 0);
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) != 0);
|
|
}
|
|
|
|
/* push/pull on D4 should still be able to force down D3 */
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) == 0);
|
|
}
|
|
print_result(failed);
|
|
}
|
|
else {
|
|
print_skipped();
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_test_input_pull_down(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO", "input pull-down");
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_IN) == 0);
|
|
if (gpio_init(ARDUINO_PIN_3, GPIO_IN_PD) == 0) {
|
|
/* give pull resistor a little time to pull */
|
|
brief_delay();
|
|
/* pull down should pull both D3 and D4 down, as D4 is connected to D3
|
|
* via the testing shield */
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) == 0);
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) == 0);
|
|
}
|
|
|
|
/* push/pull on D4 should still be able to force up D3 */
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
gpio_set(ARDUINO_PIN_4);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_3) != 0);
|
|
}
|
|
print_result(failed);
|
|
}
|
|
else {
|
|
print_skipped();
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_test_open_drain_pull_up(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO", "open-drain pull-up");
|
|
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_IN) == 0);
|
|
if (gpio_init(ARDUINO_PIN_3, GPIO_OD_PU) == 0) {
|
|
gpio_set(ARDUINO_PIN_3);
|
|
/* give pull resistor a little time to pull */
|
|
brief_delay();
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) != 0);
|
|
}
|
|
gpio_clear(ARDUINO_PIN_3);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) == 0);
|
|
}
|
|
print_result(failed);
|
|
}
|
|
else {
|
|
print_skipped();
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_test_open_drain_no_pull_up(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO", "open-drain no-pull-up");
|
|
|
|
/* we cannot test without pull up, but the input pin may have the pull
|
|
* up just as well */
|
|
if ((gpio_init(ARDUINO_PIN_3, GPIO_OD) == 0)
|
|
&& (gpio_init(ARDUINO_PIN_4, GPIO_IN_PU))) {
|
|
gpio_set(ARDUINO_PIN_3);
|
|
/* give pull resistor a little time to pull */
|
|
brief_delay();
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) != 0);
|
|
}
|
|
gpio_clear(ARDUINO_PIN_3);
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_4) == 0);
|
|
}
|
|
print_result(failed);
|
|
}
|
|
else {
|
|
print_skipped();
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_test(void)
|
|
{
|
|
bool failed = false;
|
|
|
|
failed |= periph_gpio_test_push_pull();
|
|
failed |= periph_gpio_test_input_pull_up();
|
|
failed |= periph_gpio_test_input_pull_down();
|
|
failed |= periph_gpio_test_open_drain_pull_up();
|
|
failed |= periph_gpio_test_open_drain_no_pull_up();
|
|
|
|
return failed;
|
|
}
|
|
|
|
static void gpio_cb(void *arg)
|
|
{
|
|
atomic_uint *cnt = arg;
|
|
atomic_fetch_add(cnt, 1);
|
|
}
|
|
|
|
static bool periph_gpio_irq_test_falling(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO-IRQ", "falling-edge");
|
|
atomic_uint cnt = 0;
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
ASSERT(gpio_init_int(ARDUINO_PIN_3, GPIO_IN, GPIO_FALLING, gpio_cb, &cnt) == 0);
|
|
|
|
/* no stray IRQ */
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 0);
|
|
|
|
/* no IRQ on false edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 0);
|
|
|
|
/* one IRQ on matching edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 1);
|
|
|
|
/* still no IRQ on false edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 1);
|
|
|
|
/* another IRQ on matching edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* no IRQs while disabled */
|
|
gpio_irq_disable(ARDUINO_PIN_3);
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* no stray IRQs when re-enabled */
|
|
gpio_irq_enable(ARDUINO_PIN_3);
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* still no IRQ on false edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* another IRQ on matching edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 3);
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_irq_test_rising(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO-IRQ", "rising-edge");
|
|
atomic_uint cnt = 0;
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
gpio_set(ARDUINO_PIN_4);
|
|
ASSERT(gpio_init_int(ARDUINO_PIN_3, GPIO_IN, GPIO_RISING, gpio_cb, &cnt) == 0);
|
|
|
|
/* no stray IRQ */
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 0);
|
|
|
|
/* no IRQ on false edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 0);
|
|
|
|
/* one IRQ on matching edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 1);
|
|
|
|
/* still no IRQ on false edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 1);
|
|
|
|
/* another IRQ on matching edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* no IRQs while disabled */
|
|
gpio_irq_disable(ARDUINO_PIN_3);
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* no stray IRQs when re-enabled */
|
|
gpio_irq_enable(ARDUINO_PIN_3);
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* still no IRQ on false edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* another IRQ on matching edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 3);
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_irq_test_both(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("GPIO-IRQ", "both-edges");
|
|
atomic_uint cnt = 0;
|
|
ASSERT(gpio_init(ARDUINO_PIN_4, GPIO_OUT) == 0);
|
|
gpio_set(ARDUINO_PIN_4);
|
|
ASSERT(gpio_init_int(ARDUINO_PIN_3, GPIO_IN, GPIO_BOTH, gpio_cb, &cnt) == 0);
|
|
|
|
/* no stray IRQ */
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 0);
|
|
|
|
/* IRQ on falling edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 1);
|
|
|
|
/* another IRQ on rising edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 2);
|
|
|
|
/* and another IRQ on falling edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 3);
|
|
|
|
/* and another IRQ on rising edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 4);
|
|
|
|
/* no IRQs while disabled */
|
|
gpio_irq_disable(ARDUINO_PIN_3);
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 4);
|
|
|
|
/* no stray IRQs when re-enabled */
|
|
gpio_irq_enable(ARDUINO_PIN_3);
|
|
failed |= TEST(atomic_load(&cnt) == 4);
|
|
|
|
/* an IRQ on falling edge */
|
|
gpio_clear(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 5);
|
|
|
|
/* another IRQ on rising edge */
|
|
gpio_set(ARDUINO_PIN_4);
|
|
brief_delay();
|
|
failed |= TEST(atomic_load(&cnt) == 6);
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_gpio_irq_test(void)
|
|
{
|
|
bool failed = false;
|
|
|
|
failed |= periph_gpio_irq_test_falling();
|
|
failed |= periph_gpio_irq_test_rising();
|
|
failed |= periph_gpio_irq_test_both();
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_i2c_test(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("I2C", "GPIO extender");
|
|
ASSERT(gpio_init(ARDUINO_PIN_8, GPIO_IN) == 0);
|
|
ASSERT(gpio_init(ARDUINO_PIN_9, GPIO_OUT) == 0);
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 0), GPIO_OUT) == 0);
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 1), GPIO_IN) == 0);
|
|
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
gpio_set(ARDUINO_PIN_9);
|
|
failed |= TEST(pcf857x_gpio_read(&egpios, PCF857X_GPIO_PIN(0, 1)) != 0);
|
|
gpio_clear(ARDUINO_PIN_9);
|
|
failed |= TEST(pcf857x_gpio_read(&egpios, PCF857X_GPIO_PIN(0, 1)) == 0);
|
|
}
|
|
|
|
for (unsigned i = 0; i < flaky_test_repetitions; i++) {
|
|
pcf857x_gpio_set(&egpios, PCF857X_GPIO_PIN(0, 0));
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_8) != 0);
|
|
pcf857x_gpio_clear(&egpios, PCF857X_GPIO_PIN(0, 0));
|
|
failed |= TEST(gpio_read(ARDUINO_PIN_8) == 0);
|
|
}
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static void uart_rx_cb(void *arg, uint8_t data)
|
|
{
|
|
(void)arg;
|
|
if (serial_buf.pos < sizeof(serial_buf.data)) {
|
|
serial_buf.data[serial_buf.pos] = data;
|
|
}
|
|
serial_buf.pos++;
|
|
}
|
|
|
|
static bool periph_uart_rxtx_test(uint32_t symbolrate)
|
|
{
|
|
bool failed = 0;
|
|
uint16_t duration_ticks = 0;
|
|
uint16_t bit_ticks = 0;
|
|
uint16_t start;
|
|
memset(&serial_buf, 0, sizeof(serial_buf));
|
|
|
|
ASSERT(uart_init(UART_TEST_DEV, symbolrate, uart_rx_cb, NULL) == 0);
|
|
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
bit_ticks = TIMER_FREQ_UART_TEST / symbolrate;
|
|
duration_ticks = 9ULL * sizeof(testdata) * bit_ticks;
|
|
}
|
|
|
|
/* test that data send matches data received */
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
start = timer_read(TIMER);
|
|
}
|
|
uart_write(UART_TEST_DEV, (void *)testdata, sizeof(testdata));
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
uint16_t stop = timer_read(TIMER);
|
|
/* expecting actual duration within 75% to 200% of the expected. */
|
|
failed |= TEST(stop - start > duration_ticks - (duration_ticks >> 2));
|
|
failed |= TEST(stop - start < (duration_ticks << 1));
|
|
if (failed) {
|
|
DEBUG("%" PRIu32 " Bd, expected %" PRIu16 " ticks, got %" PRIu16
|
|
" ticks\n",
|
|
symbolrate, duration_ticks, (uint16_t)(stop - start));
|
|
}
|
|
}
|
|
long_delay();
|
|
failed |= TEST(memcmp(testdata, serial_buf.data, sizeof(serial_buf.data)) == 0);
|
|
failed |= TEST(serial_buf.pos == sizeof(testdata));
|
|
|
|
/* test that no data is received when UART is off */
|
|
uart_poweroff(UART_TEST_DEV);
|
|
uart_write(UART_TEST_DEV, (void *)testdata, sizeof(testdata));
|
|
long_delay();
|
|
failed |= TEST(serial_buf.pos == sizeof(testdata));
|
|
|
|
/* test that data is received again when UART is back on */
|
|
memset(&serial_buf, 0, sizeof(serial_buf));
|
|
uart_poweron(UART_TEST_DEV);
|
|
long_delay();
|
|
/* no data expected yet */
|
|
failed |= TEST(serial_buf.pos == 0);
|
|
/* now writing with device back online */
|
|
uart_write(UART_TEST_DEV, (void *)testdata, sizeof(testdata));
|
|
long_delay();
|
|
failed |= TEST(memcmp(testdata, serial_buf.data, sizeof(serial_buf.data)) == 0);
|
|
failed |= TEST(serial_buf.pos == sizeof(testdata));
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_uart_test_slow(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("UART", "slow");
|
|
failed |= periph_uart_rxtx_test(9600);
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_uart_test_fast(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("UART", "fast");
|
|
failed |= periph_uart_rxtx_test(115200);
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_uart_test(void)
|
|
{
|
|
bool failed = false;
|
|
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
ASSERT(timer_init(TIMER, TIMER_FREQ_UART_TEST, NULL, NULL) == 0);
|
|
timer_start(TIMER);
|
|
}
|
|
|
|
failed |= periph_uart_test_slow();
|
|
failed |= periph_uart_test_fast();
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_spi_rxtx_test(spi_t bus, spi_mode_t mode, spi_clk_t clk,
|
|
uint32_t clk_hz, gpio_t clk_check, bool idle_level,
|
|
const char *test_in_detail)
|
|
{
|
|
(void)test_in_detail;
|
|
bool failed = 0;
|
|
print_start("SPI", test_in_detail);
|
|
uint16_t byte_transfer_ticks = 0;
|
|
memset(&serial_buf, 0, sizeof(serial_buf));
|
|
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
byte_transfer_ticks = 8ULL * TIMER_FREQ_UART_TEST / clk_hz;
|
|
}
|
|
|
|
/* D10 is C̅S̅, D7 is connected to C̅S̅ */
|
|
spi_cs_t cs = ARDUINO_PIN_10;
|
|
gpio_t cs_check = ARDUINO_PIN_7;
|
|
gpio_init(cs_check, GPIO_IN);
|
|
spi_init_cs(bus, cs);
|
|
|
|
spi_acquire(bus, cs, mode, clk);
|
|
|
|
if (IS_USED(MODULE_PCF857X)) {
|
|
failed |= TEST(idle_level == (bool)pcf857x_gpio_read(&egpios, clk_check));
|
|
}
|
|
|
|
/* C̅S̅ should still be HIGH while no chip is selected */
|
|
failed |= TEST(gpio_read(cs_check) != 0);
|
|
|
|
for (uint8_t i = 0; i < UINT8_MAX; i++) {
|
|
uint16_t start = 0;
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
start = timer_read(TIMER);
|
|
}
|
|
uint8_t received = spi_transfer_byte(bus, cs, true, i);
|
|
uint16_t stop = 0;
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
stop = timer_read(TIMER);
|
|
}
|
|
failed |= TEST(received == i);
|
|
uint16_t byte_time = (uint16_t)(stop - start);
|
|
/* We allow the actual SPI clock to be slower than requested, but not
|
|
* faster. So the transfer needs to take *at least* the theoretical
|
|
* time. Given the overhead of, this already has some room for error */
|
|
failed |= TEST(byte_time >= byte_transfer_ticks);
|
|
/* C̅S̅ should be still LOW while chip is selected */
|
|
failed |= TEST(gpio_read(cs_check) == 0);
|
|
}
|
|
|
|
failed |= TEST(spi_transfer_byte(bus, cs, false, UINT8_MAX) == UINT8_MAX);
|
|
/* C̅S̅ should be again HIGH while now that no chip is selected */
|
|
failed |= TEST(gpio_read(cs_check) != 0);
|
|
|
|
/* no also test for different sizes */
|
|
for (unsigned i = 1; i <= sizeof(testdata); i++) {
|
|
uint8_t target[sizeof(testdata) + 4];
|
|
memset(target, 0x55, sizeof(target));
|
|
/* C̅S̅ should be HIGH before chip is selected */
|
|
failed |= TEST(gpio_read(cs_check) != 0);
|
|
spi_transfer_bytes(bus, cs, false, testdata, target, i);
|
|
/* C̅S̅ should be HIGH again after transfer */
|
|
failed |= TEST(gpio_read(cs_check) != 0);
|
|
|
|
/* first part of data read should be the test data */
|
|
for (unsigned j = 0; j < i; j++) {
|
|
failed |= TEST(target[j] == testdata[j]);
|
|
}
|
|
|
|
/* rest of the target should be the canary value */
|
|
for (unsigned j = i; j < sizeof(testdata); j++) {
|
|
failed |= TEST(target[j] == 0x55);
|
|
}
|
|
}
|
|
|
|
if (IS_USED(MODULE_PCF857X)) {
|
|
failed |= TEST(idle_level == (bool)pcf857x_gpio_read(&egpios, clk_check));
|
|
}
|
|
|
|
spi_release(bus);
|
|
print_result(failed);
|
|
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_spi_test(void)
|
|
{
|
|
if (IS_USED(MODULE_PERIPH_TIMER)) {
|
|
ASSERT(timer_init(TIMER, TIMER_FREQ_SPI_TEST, NULL, NULL) == 0);
|
|
timer_start(TIMER);
|
|
}
|
|
|
|
bool failed = false;
|
|
static const spi_clk_t clocks[] = { SPI_CLK_100KHZ, SPI_CLK_1MHZ, SPI_CLK_10MHZ };
|
|
static const uint32_t clk_hzs[] = { KHZ(100), MHZ(1), MHZ(10) };
|
|
|
|
if (IS_USED(MODULE_PCF857X)) {
|
|
for (int i = 0; i < (int)ARRAY_SIZE(spi_clk_check_pins); i++) {
|
|
ASSERT(pcf857x_gpio_init(&egpios, spi_clk_check_pins[i], GPIO_IN) == 0);
|
|
}
|
|
}
|
|
|
|
/* using a signed comparison here to also compile when no SPI buses are
|
|
* available for testing */
|
|
for (int i = 0; i < (int)ARRAY_SIZE(spi_buses); i++) {
|
|
spi_t bus = spi_buses[i];
|
|
gpio_t clk_check = spi_clk_check_pins[i];
|
|
for (unsigned j = 0; j < ARRAY_SIZE(clocks); j++) {
|
|
spi_clk_t clk = clocks[j];
|
|
uint32_t clk_hz = clk_hzs[j];
|
|
failed |= periph_spi_rxtx_test(bus, SPI_MODE_0, clk, clk_hz, clk_check, false, "mode 0");
|
|
failed |= periph_spi_rxtx_test(bus, SPI_MODE_1, clk, clk_hz, clk_check, false, "mode 1");
|
|
failed |= periph_spi_rxtx_test(bus, SPI_MODE_2, clk, clk_hz, clk_check, true, "mode 2");
|
|
failed |= periph_spi_rxtx_test(bus, SPI_MODE_3, clk, clk_hz, clk_check, true, "mode 3");
|
|
}
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_pwm_test_chan(pwm_t pwm_dev, uint8_t pwm_chan, pwm_mode_t pwm_mode, adc_t adc_line)
|
|
{
|
|
bool failed = false;
|
|
print_start("PWM", "duty cycle (via ADC)");
|
|
|
|
failed |= TEST(pwm_init(pwm_dev, pwm_mode, CLOCK_CORECLOCK, 256) > 0);
|
|
if (failed) {
|
|
/* pwm mode not supported, most likely */
|
|
return failed;
|
|
}
|
|
|
|
ASSERT(adc_init(adc_line) == 0);
|
|
|
|
for (uint16_t i = 0; i <= UINT8_MAX; i++) {
|
|
pwm_set(pwm_dev, pwm_chan, i);
|
|
/* give voltage at ADC some time to settle */
|
|
brief_delay();
|
|
uint16_t sample = adc_sample(adc_line, ADC_RES_10BIT);
|
|
uint16_t expected = i << 2;
|
|
|
|
/* let's allow for quite some error here */
|
|
const uint16_t delta = 64;
|
|
uint16_t lower = expected <= delta ? 0 : expected - delta;
|
|
uint16_t upper = MIN(1023, expected + delta);
|
|
bool test_failed = TEST((lower <= sample) && (upper >= sample));
|
|
if (test_failed) {
|
|
printf("%" PRIu16 " <= %" PRIu16 " <= %" PRIu16": FAILED\n",
|
|
lower, sample, upper);
|
|
}
|
|
failed |= test_failed;
|
|
}
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
static bool periph_pwm_test(void)
|
|
{
|
|
bool failed = false;
|
|
static const pwm_mode_t modes[] = { PWM_LEFT, PWM_RIGHT, PWM_CENTER };
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(modes); i++) {
|
|
if (ENABLE_PWM_TEST_D5) {
|
|
failed |= periph_pwm_test_chan(ARDUINO_PIN_5_PWM_DEV, ARDUINO_PIN_5_PWM_CHAN, modes[i], ARDUINO_A2);
|
|
}
|
|
if (ENABLE_PWM_TEST_D6) {
|
|
failed |= periph_pwm_test_chan(ARDUINO_PIN_6_PWM_DEV, ARDUINO_PIN_6_PWM_CHAN, modes[i], ARDUINO_A1);
|
|
}
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
static void r_2r_dac_init(void)
|
|
{
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 4), GPIO_OUT) == 0);
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 5), GPIO_OUT) == 0);
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 6), GPIO_OUT) == 0);
|
|
ASSERT(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 7), GPIO_OUT) == 0);
|
|
}
|
|
|
|
static void r_2r_dac_write(uint8_t val)
|
|
{
|
|
pcf857x_gpio_write(&egpios, PCF857X_GPIO_PIN(0, 4), val & (1U << 3));
|
|
pcf857x_gpio_write(&egpios, PCF857X_GPIO_PIN(0, 5), val & (1U << 2));
|
|
pcf857x_gpio_write(&egpios, PCF857X_GPIO_PIN(0, 6), val & (1U << 1));
|
|
pcf857x_gpio_write(&egpios, PCF857X_GPIO_PIN(0, 7), val & (1U << 0));
|
|
}
|
|
|
|
static bool periph_adc_test(void)
|
|
{
|
|
bool failed = false;
|
|
print_start("ADC", "sample R2R DAC output");
|
|
|
|
adc_init(ARDUINO_A0);
|
|
r_2r_dac_init();
|
|
|
|
for (uint8_t i = 0; i < 16; i++) {
|
|
r_2r_dac_write(i);
|
|
uint16_t sample = adc_sample(ARDUINO_A0, ADC_RES_10BIT);
|
|
uint16_t expected = i << 6;
|
|
|
|
/* the resistors are said to be rather accurate, so lets be a bit
|
|
* more strict here */
|
|
const uint16_t delta = 16;
|
|
uint16_t lower = expected <= delta ? 0 : expected - delta;
|
|
uint16_t upper = MIN(1023, expected + delta);
|
|
bool test_failed = TEST((lower <= sample) && (upper >= sample));
|
|
if (test_failed) {
|
|
printf("%" PRIu16 " <= %" PRIu16 " <= %" PRIu16": FAILED\n",
|
|
lower, sample, upper);
|
|
}
|
|
failed |= test_failed;
|
|
}
|
|
|
|
print_result(failed);
|
|
return failed;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
bool failed = false;
|
|
|
|
printf("self-testing peripheral drivers\n"
|
|
"===============================\n");
|
|
|
|
/* the GPIO extender is used by the I2C test and the ADC test, so only
|
|
* initialize it once here */
|
|
if (IS_USED(MODULE_PCF857X)) {
|
|
ASSERT(pcf857x_init(&egpios, ¶ms) == PCF857X_OK);
|
|
}
|
|
|
|
if (IS_USED(MODULE_PERIPH_GPIO)) {
|
|
failed |= periph_gpio_test();
|
|
}
|
|
|
|
if (IS_USED(MODULE_PERIPH_GPIO_IRQ)) {
|
|
failed |= periph_gpio_irq_test();
|
|
}
|
|
|
|
if (IS_USED(MODULE_PCF857X) && IS_USED(MODULE_PERIPH_GPIO)) {
|
|
failed |= periph_i2c_test();
|
|
}
|
|
|
|
if (ENABLE_UART_TEST) {
|
|
failed |= periph_uart_test();
|
|
}
|
|
|
|
if (IS_USED(MODULE_PERIPH_SPI)) {
|
|
failed |= periph_spi_test();
|
|
}
|
|
|
|
if (ENABLE_PWM_TEST) {
|
|
failed |= periph_pwm_test();
|
|
}
|
|
|
|
if (ENABLE_ADC_TEST) {
|
|
failed |= periph_adc_test();
|
|
}
|
|
|
|
if (failed) {
|
|
printf("\n\nSOME TESTS FAILED\n");
|
|
return 1;
|
|
}
|
|
|
|
printf("\n\nALL TESTS SUCCEEDED\n");
|
|
return 0;
|
|
}
|