[utest][pin] add pin loopback utest (#11445)

* [utest][pin] add pin loopback utest

* [utest][pin] add BSP CI coverage

* [utest][pin] fix BSP CI config

* [utest][pin] fix ra8p1 utest link section
This commit is contained in:
CYFS
2026-06-22 16:03:58 +08:00
committed by GitHub
parent 7071ba35cb
commit f922adeb07
10 changed files with 437 additions and 0 deletions
@@ -0,0 +1,11 @@
scons.args: &scons
scons_arg:
- '--strict'
# ------ utest CI ------
utest.pin:
<<: *scons
kconfig:
- CONFIG_RT_USING_UTESTCASES=y
- CONFIG_RT_CONSOLEBUF_SIZE=256
- CONFIG_RT_UTEST_PIN=y
@@ -31,3 +31,11 @@ Peripheral.arduino:
kconfig:
- CONFIG_BSP_USING_ARDUINO=y
# ------ utest CI ------
utest.pin:
<<: *scons
kconfig:
- CONFIG_RT_USING_UTESTCASES=y
- CONFIG_RT_CONSOLEBUF_SIZE=256
- CONFIG_RT_UTEST_PIN=y
@@ -38,3 +38,10 @@ devices.ethernet:
devices.rtc:
kconfig:
- CONFIG_BSP_USING_ONCHIP_RTC=y
# ------ utest CI ------
utest.pin:
kconfig:
- CONFIG_RT_USING_UTESTCASES=y
- CONFIG_RT_CONSOLEBUF_SIZE=256
- CONFIG_RT_UTEST_PIN=y
@@ -455,6 +455,12 @@ SECTIONS
KEEP(*(VSymTab))
__vsymtab_end = .;
/* section information for utest */
. = ALIGN(4);
__rt_utest_tc_tab_start = .;
KEEP(*(UtestTcTab))
__rt_utest_tc_tab_end = .;
/* section information for initial. */
. = ALIGN(4);
__rt_init_start = .;
@@ -195,6 +195,13 @@ peripheral.sram:
peripheral.usb_mouse:
kconfig:
- CONFIG_BSP_USING_USB_MOUSE=y
# ------ utest CI ------
utest.pin:
<<: *scons
kconfig:
- CONFIG_RT_USING_UTESTCASES=y
- CONFIG_RT_CONSOLEBUF_SIZE=256
- CONFIG_RT_UTEST_PIN=y
# ------ component CI ------
component.cherryusb_cdc:
kconfig:
+6
View File
@@ -1,3 +1,4 @@
import os
from building import *
group = []
@@ -21,4 +22,9 @@ if GetDepend(['RT_PIN_PL061']):
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
list = os.listdir(cwd)
for item in list:
if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
group = group + SConscript(os.path.join(item, 'SConscript'))
Return('group')
+48
View File
@@ -0,0 +1,48 @@
menu "Pin Test"
depends on RT_USING_PIN
config RT_UTEST_PIN
bool "Pin Loopback Test"
default n
if RT_UTEST_PIN
config RT_UTEST_PIN_OUT_NAME
string "Output Pin Name for Pin Test"
default ""
help
Optional hardware pin name, such as PA.0 or P0.12. Leave empty to
use RT_UTEST_PIN_OUT_NUM.
config RT_UTEST_PIN_IN_NAME
string "Input Pin Name for Pin Test"
default ""
help
Optional hardware pin name, such as PA.1 or P0.13. Leave empty to
use RT_UTEST_PIN_IN_NUM.
config RT_UTEST_PIN_OUT_NUM
int "Output Pin Number for Pin Test"
default -1
config RT_UTEST_PIN_IN_NUM
int "Input Pin Number for Pin Test"
default -1
config RT_UTEST_PIN_SETTLE_MS
int "Settle Delay in Milliseconds"
range 0 1000
default 2
config RT_UTEST_PIN_IRQ_TIMEOUT_MS
int "IRQ Timeout in Milliseconds"
range 1 60000
default 500
config RT_UTEST_PIN_IRQ
bool "Pin IRQ Loopback Test"
default y
endif
endmenu
+13
View File
@@ -0,0 +1,13 @@
Import('rtconfig')
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd]
if GetDepend(['RT_UTEST_PIN']):
src += Glob('*.c')
group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_PIN'], CPPPATH = CPPPATH)
Return('group')
+330
View File
@@ -0,0 +1,330 @@
/*
* Copyright (c) 2006-2026 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2026-06-08 CYFS add generic pin loopback utest
*/
/**
* Test Case Name: Pin Two-wire Loopback Test
*
* Test Objectives:
* - Validate the generic RT-Thread pin APIs with two externally shorted pins.
* - Verify APIs: rt_device_find, rt_pin_get, rt_pin_mode, rt_pin_write,
* rt_pin_read, rt_pin_attach_irq, rt_pin_irq_enable, rt_pin_detach_irq.
*
* Test Scenarios:
* - **Scenario 1 (GPIO level loopback / pin_loopback_level):**
* 1. Resolve the configured output/input pins.
* 2. Configure one pin as output and the shorted peer as input.
* 3. Drive low and high levels from the output pin, then confirm the input
* pin follows each level.
* 4. Reverse direction and repeat the level check to cover both pins.
* - **Scenario 2 (GPIO IRQ loopback / pin_loopback_irq):**
* 1. Configure the output pin low and attach an interrupt on the input pin.
* 2. Toggle the output pin to generate rising, falling, and both-edge events.
* 3. Wait for the interrupt callback after each generated edge.
*
* Dependencies:
* - Requires `RT_UTEST_PIN` and two physical GPIO pins shorted together.
* - Set `RT_UTEST_PIN_OUT_NAME`/`RT_UTEST_PIN_IN_NAME` or numeric
* `RT_UTEST_PIN_OUT_NUM`/`RT_UTEST_PIN_IN_NUM` for the connected pins.
* - IRQ scenario requires `RT_UTEST_PIN_IRQ` and pin IRQ support in the BSP.
*
* Expected Results:
* - Input level always matches the driven output level.
* - If IRQ testing is enabled and supported, all configured edge callbacks
* arrive before timeout.
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "utest.h"
#ifdef RT_UTEST_PIN
struct pin_loopback_ctx
{
rt_base_t out_pin;
rt_base_t in_pin;
};
static struct pin_loopback_ctx loopback_ctx;
#ifdef RT_UTEST_PIN_IRQ
static volatile rt_uint32_t irq_count;
#endif
static rt_bool_t pin_name_configured(const char *name)
{
return (name != RT_NULL) && (name[0] != '\0');
}
static rt_base_t pin_resolve(const char *name, rt_base_t number)
{
if (pin_name_configured(name))
{
return rt_pin_get(name);
}
return number;
}
static void pin_wait_settle(void)
{
rt_thread_mdelay(RT_UTEST_PIN_SETTLE_MS);
}
static void pin_prepare_loopback(rt_base_t drive_pin, rt_base_t sense_pin)
{
rt_pin_mode(sense_pin, PIN_MODE_INPUT);
rt_pin_mode(drive_pin, PIN_MODE_OUTPUT);
rt_pin_write(drive_pin, PIN_LOW);
pin_wait_settle();
}
static void pin_drive_and_check(rt_base_t drive_pin, rt_base_t sense_pin, rt_uint8_t level)
{
rt_pin_write(drive_pin, level);
pin_wait_settle();
uassert_int_equal((int)rt_pin_read(sense_pin), (int)level);
}
static void pin_loopback_level(void)
{
pin_prepare_loopback(loopback_ctx.out_pin, loopback_ctx.in_pin);
pin_drive_and_check(loopback_ctx.out_pin, loopback_ctx.in_pin, PIN_LOW);
pin_drive_and_check(loopback_ctx.out_pin, loopback_ctx.in_pin, PIN_HIGH);
pin_prepare_loopback(loopback_ctx.in_pin, loopback_ctx.out_pin);
pin_drive_and_check(loopback_ctx.in_pin, loopback_ctx.out_pin, PIN_LOW);
pin_drive_and_check(loopback_ctx.in_pin, loopback_ctx.out_pin, PIN_HIGH);
rt_pin_write(loopback_ctx.in_pin, PIN_LOW);
}
#ifdef RT_UTEST_PIN_IRQ
static rt_tick_t pin_irq_timeout_ticks(void)
{
rt_tick_t ticks;
ticks = rt_tick_from_millisecond(RT_UTEST_PIN_IRQ_TIMEOUT_MS);
if (ticks == 0)
{
ticks = 1;
}
return ticks;
}
static void pin_irq_handler(void *args)
{
RT_UNUSED(args);
irq_count++;
}
static rt_err_t pin_irq_wait_count(rt_uint32_t expected_count)
{
rt_tick_t tick_start;
rt_tick_t timeout;
tick_start = rt_tick_get();
timeout = pin_irq_timeout_ticks();
while (irq_count < expected_count)
{
if (rt_tick_get_delta(tick_start) >= timeout)
{
return -RT_ETIMEOUT;
}
rt_thread_mdelay(1);
}
return RT_EOK;
}
static rt_err_t pin_irq_cleanup(rt_base_t pin)
{
rt_err_t ret;
ret = rt_pin_irq_enable(pin, PIN_IRQ_DISABLE);
if (ret != RT_EOK)
{
return ret;
}
return rt_pin_detach_irq(pin);
}
static rt_err_t pin_irq_attach_enable(rt_uint8_t mode)
{
rt_err_t ret;
ret = rt_pin_attach_irq(loopback_ctx.in_pin, mode, pin_irq_handler, RT_NULL);
if (ret != RT_EOK)
{
return ret;
}
ret = rt_pin_irq_enable(loopback_ctx.in_pin, PIN_IRQ_ENABLE);
if (ret != RT_EOK)
{
rt_pin_detach_irq(loopback_ctx.in_pin);
}
return ret;
}
static void pin_trigger_rising(void)
{
rt_pin_write(loopback_ctx.out_pin, PIN_LOW);
pin_wait_settle();
rt_pin_write(loopback_ctx.out_pin, PIN_HIGH);
}
static void pin_trigger_falling(void)
{
rt_pin_write(loopback_ctx.out_pin, PIN_HIGH);
pin_wait_settle();
rt_pin_write(loopback_ctx.out_pin, PIN_LOW);
}
static void pin_loopback_irq_rising(void)
{
rt_err_t ret;
pin_prepare_loopback(loopback_ctx.out_pin, loopback_ctx.in_pin);
irq_count = 0;
ret = pin_irq_attach_enable(PIN_IRQ_MODE_RISING);
if (ret == -RT_ENOSYS)
{
LOG_W("pin irq is not supported, skip rising-edge irq check");
return;
}
uassert_int_equal(ret, RT_EOK);
if (ret != RT_EOK)
{
return;
}
pin_trigger_rising();
uassert_int_equal(pin_irq_wait_count(1), RT_EOK);
uassert_int_equal((int)irq_count, 1);
uassert_int_equal(pin_irq_cleanup(loopback_ctx.in_pin), RT_EOK);
}
static void pin_loopback_irq_falling(void)
{
rt_err_t ret;
pin_prepare_loopback(loopback_ctx.out_pin, loopback_ctx.in_pin);
irq_count = 0;
ret = pin_irq_attach_enable(PIN_IRQ_MODE_FALLING);
if (ret == -RT_ENOSYS)
{
LOG_W("pin irq is not supported, skip falling-edge irq check");
return;
}
uassert_int_equal(ret, RT_EOK);
if (ret != RT_EOK)
{
return;
}
pin_trigger_falling();
uassert_int_equal(pin_irq_wait_count(1), RT_EOK);
uassert_int_equal((int)irq_count, 1);
uassert_int_equal(pin_irq_cleanup(loopback_ctx.in_pin), RT_EOK);
}
static void pin_loopback_irq_both_edges(void)
{
rt_err_t ret;
pin_prepare_loopback(loopback_ctx.out_pin, loopback_ctx.in_pin);
irq_count = 0;
ret = pin_irq_attach_enable(PIN_IRQ_MODE_RISING_FALLING);
if (ret == -RT_ENOSYS)
{
LOG_W("pin irq is not supported, skip both-edge irq check");
return;
}
uassert_int_equal(ret, RT_EOK);
if (ret != RT_EOK)
{
return;
}
pin_trigger_rising();
uassert_int_equal(pin_irq_wait_count(1), RT_EOK);
pin_trigger_falling();
uassert_int_equal(pin_irq_wait_count(2), RT_EOK);
uassert_int_equal((int)irq_count, 2);
uassert_int_equal(pin_irq_cleanup(loopback_ctx.in_pin), RT_EOK);
}
#endif /* RT_UTEST_PIN_IRQ */
static rt_err_t utest_tc_init(void)
{
if ((rt_device_find("pin") == RT_NULL) && (rt_device_find("gpio") == RT_NULL))
{
LOG_E("pin/gpio device is not registered");
return -RT_ERROR;
}
loopback_ctx.out_pin = pin_resolve(RT_UTEST_PIN_OUT_NAME, RT_UTEST_PIN_OUT_NUM);
loopback_ctx.in_pin = pin_resolve(RT_UTEST_PIN_IN_NAME, RT_UTEST_PIN_IN_NUM);
if ((loopback_ctx.out_pin < 0) || (loopback_ctx.in_pin < 0))
{
LOG_E("invalid pin test config: out=%d in=%d", loopback_ctx.out_pin, loopback_ctx.in_pin);
return -RT_ERROR;
}
if (loopback_ctx.out_pin == loopback_ctx.in_pin)
{
LOG_E("pin test output and input pins must be different");
return -RT_ERROR;
}
LOG_I("pin loopback test uses output pin %d and input pin %d", loopback_ctx.out_pin, loopback_ctx.in_pin);
return RT_EOK;
}
static rt_err_t utest_tc_cleanup(void)
{
#ifdef RT_UTEST_PIN_IRQ
rt_pin_irq_enable(loopback_ctx.in_pin, PIN_IRQ_DISABLE);
rt_pin_detach_irq(loopback_ctx.in_pin);
#endif
rt_pin_mode(loopback_ctx.in_pin, PIN_MODE_INPUT);
rt_pin_mode(loopback_ctx.out_pin, PIN_MODE_OUTPUT);
rt_pin_write(loopback_ctx.out_pin, PIN_LOW);
rt_pin_mode(loopback_ctx.out_pin, PIN_MODE_INPUT);
return RT_EOK;
}
static void testcase(void)
{
UTEST_UNIT_RUN(pin_loopback_level);
#ifdef RT_UTEST_PIN_IRQ
UTEST_UNIT_RUN(pin_loopback_irq_rising);
UTEST_UNIT_RUN(pin_loopback_irq_falling);
UTEST_UNIT_RUN(pin_loopback_irq_both_edges);
#endif
}
UTEST_TC_EXPORT(testcase, "components.drivers.pin.loopback", utest_tc_init, utest_tc_cleanup, 20);
#endif /* RT_UTEST_PIN */
+1
View File
@@ -16,6 +16,7 @@ menu "RT-Thread Utestcases"
rsource "../../../components/drivers/core/utest/Kconfig"
rsource "../../../components/drivers/audio/utest/Kconfig"
rsource "../../../components/drivers/ipc/utest/Kconfig"
rsource "../../../components/drivers/pin/utest/Kconfig"
rsource "../../../components/drivers/serial/utest/Kconfig"
rsource "../../../components/drivers/smp_call/utest/Kconfig"
endmenu