mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-08-18 11:53:05 +08:00
[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:
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user