The issue is that the radiated power at 2480 MHz during the Radio Test is 10 dB lower compared to other channels.

We developed firmware based on the sample provided at the following link to pass Japan's technical standards compliance test
https://docs.nordicsemi.com/bundle/sdk_nrf5_v17.0.2/page/nrf_radio_test_example.html.

However, during testing, we received feedback that the transmission power drops specifically at 2480 MHz. It appears that the radiated power at 2480 MHz is 10 dB lower compared to other channels.

  • 2402 MHz: 0.000550 W
  • 2442 MHz: 0.000339 W
  • 2480 MHz: 0.000047 W

We are using the Proteus-3 module with an nRF52840:
https://www.we-online.com/en/components/products/PROTEUS-III.

Below is the code we are using.
Are there any suspicious points in the implementation?
The transmission power should always be fixed at the maximum 8 dBm.

In radio_cmd_handler.c, we have the following definition:

static radio_param_config_t m_config = { .tx_pattern = TRANSMIT_PATTERN_RANDOM, .mode = NRF_RADIO_MODE_BLE_1MBIT, .txpower = NRF_RADIO_TXPOWER_POS8DBM, .channel_start = 0, .channel_end = 80, .delay_ms = 10, .duty_cycle = 50, };

Since txpower is set to NRF_RADIO_TXPOWER_POS8DBM and we have not changed this value, we assume the transmission power remains fixed at +8 dBm.

Therefore, we are uncertain why the power drops only at 2480 MHz.

// ============================================================
// radio_test.c

#include <stdbool.h>

#include "app_util_platform.h"
#include "nrf.h"

#include "nrf_nvmc.h"
#include "nrf_radio.h"
#include "nrf_rng.h"
#include "nrfx_timer.h"

#include "radio_test.h"

#define NRF_LOG_MODULE_NAME radio_test
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();
#include "nrf_log_ctrl.h"

#define IEEE_DEFAULT_FREQ (5)           /**< IEEE 802.15.4 default frequency. */
#define RADIO_LENGTH_LENGTH_FIELD (8UL) /**< Length on air of the LENGTH field. */

#define IEEE_FREQ_CALC(_channel) (IEEE_DEFAULT_FREQ + \
                                  (IEEE_DEFAULT_FREQ * ((_channel) - IEEE_MIN_CHANNEL))) /**< Frequency calculation for a given channel in the IEEE 802.15.4 radio mode. */
#define CHAN_TO_FREQ(_channel) (2400 + _channel)                                         /**< Frequency calculation for a given channel. */

static uint8_t m_tx_packet[RADIO_MAX_PAYLOAD_LEN]; /**< Buffer for the radio TX packet. */
static uint8_t m_rx_packet[RADIO_MAX_PAYLOAD_LEN]; /**< Buffer for the radio RX packet. */
static uint32_t m_tx_packet_cnt;                   /**< Number of transmitted packets. */
static uint32_t m_rx_packet_cnt;                   /**< Number of received packets with valid CRC. */
static uint8_t m_current_channel;                  /**< Radio current channel (frequency). */
// static const nrfx_timer_t m_timer = NRFX_TIMER_INSTANCE(1); /**< Timer used for channel sweeps and tx with duty cycle. */
static const radio_test_config_t* m_p_test_config = NULL; /**< Test configuration descriptor. */

/**
 * @brief Function for generating an 8-bit random number with the internal random generator.
 */
static uint8_t rnd8(void) {
    NRF_LOG_INFO("%s", __func__);
    nrf_rng_event_clear(NRF_RNG_EVENT_VALRDY);

    while (!nrf_rng_event_get(NRF_RNG_EVENT_VALRDY)) {
        /* Do nothing. */
    }

    return nrf_rng_random_value_get();
}

/**brief Function for setting the channel for radio.
 *
 * @param[in] mode    Radio mode.
 * @param[in] channel Radio channel.
 */
static void radio_channel_set(nrf_radio_mode_t mode, uint8_t channel) {
    NRF_LOG_INFO("%s. mode:%d, channel:%d", __func__, mode, channel);

#if USE_MORE_RADIO_MODES
    if (mode == NRF_RADIO_MODE_IEEE802154_250KBIT) {
        if ((channel >= IEEE_MIN_CHANNEL) && (channel <= IEEE_MAX_CHANNEL)) {
            nrf_radio_frequency_set(CHAN_TO_FREQ(IEEE_FREQ_CALC(channel)));
        } else {
            nrf_radio_frequency_set(CHAN_TO_FREQ(IEEE_DEFAULT_FREQ));
        }
    } else {
        nrf_radio_frequency_set(CHAN_TO_FREQ(channel));
    }
#else
    nrf_radio_frequency_set(CHAN_TO_FREQ(channel));
#endif /* USE_MORE_RADIO_MODES */
}

/**@brief Function for configuring the radio in every possible mode.
 *
 * @param[in] mode    Radio mode.
 * @param[in] pattern Radio transmission pattern.
 */
static void radio_config(nrf_radio_mode_t mode, transmit_pattern_t pattern) {
    nrf_radio_packet_conf_t packet_conf;

    /* Reset Radio ramp-up time. */
    nrf_radio_modecnf0_set(false, RADIO_MODECNF0_DTX_Center);
    nrf_radio_crc_configure(RADIO_CRCCNF_LEN_Disabled, NRF_RADIO_CRC_ADDR_INCLUDE, 0);

    /* Set the device address 0 to use when transmitting. */
    nrf_radio_txaddress_set(0);
    /* Enable the device address 0 to use to select which addresses to
     * receive
     */
    nrf_radio_rxaddresses_set(1);

    /* Set the address according to the transmission pattern. */
    switch (pattern) {
    case TRANSMIT_PATTERN_RANDOM:
        nrf_radio_prefix0_set(0xAB);
        nrf_radio_base0_set(0xABABABAB);
        break;

    case TRANSMIT_PATTERN_11001100:
        nrf_radio_prefix0_set(0xCC);
        nrf_radio_base0_set(0xCCCCCCCC);
        break;

    case TRANSMIT_PATTERN_11110000:
        nrf_radio_prefix0_set(0x6A);
        nrf_radio_base0_set(0x58FE811B);
        break;

    default:
        return;
    }

    /* Packet configuration:
     * payload length size = 8 bits,
     * 0-byte static length, max 255-byte payload,
     * 4-byte base address length (5-byte full address length),
     * Bit 24: 1 Big endian,
     * Bit 25: 1 Whitening enabled.
     */
    memset(&packet_conf, 0, sizeof(packet_conf));
    packet_conf.lflen = RADIO_LENGTH_LENGTH_FIELD;
    packet_conf.maxlen = (sizeof(m_tx_packet) - 1);
    packet_conf.statlen = 0;
    packet_conf.balen = 4;
    packet_conf.big_endian = true;
    packet_conf.whiteen = true;

    switch (mode) {
#if USE_MORE_RADIO_MODES
    case NRF_RADIO_MODE_IEEE802154_250KBIT:
        /* Packet configuration:
         * S1 size = 0 bits,
         * S0 size = 0 bytes,
         * 32-bit preamble.
         */
        packet_conf.plen = NRF_RADIO_PREAMBLE_LENGTH_32BIT_ZERO;
        packet_conf.maxlen = IEEE_MAX_PAYLOAD_LEN;
        packet_conf.balen = 0;
        packet_conf.big_endian = false;
        packet_conf.whiteen = false;

        /* Set fast ramp-up time. */
        nrf_radio_modecnf0_set(true, RADIO_MODECNF0_DTX_Center);
        break;

    case NRF_RADIO_MODE_BLE_LR500KBIT:
    case NRF_RADIO_MODE_BLE_LR125KBIT:
        /* Packet configuration:
         * S1 size = 0 bits,
         * S0 size = 0 bytes,
         * 10-bit preamble.
         */
        packet_conf.plen = NRF_RADIO_PREAMBLE_LENGTH_LONG_RANGE;
        packet_conf.maxlen = IEEE_MAX_PAYLOAD_LEN;
        packet_conf.cilen = 2;
        packet_conf.termlen = 3;
        packet_conf.big_endian = false;
        packet_conf.balen = 3;

        /* Set fast ramp-up time. */
        nrf_radio_modecnf0_set(true, RADIO_MODECNF0_DTX_Center);

        /* Set CRC length; CRC calculation does not include the address
         * field.
         */
        nrf_radio_crc_configure(RADIO_CRCCNF_LEN_Three, NRF_RADIO_CRC_ADDR_SKIP, 0);
        break;
#endif /* USE_MORE_RADIO_MODES */

    case NRF_RADIO_MODE_BLE_2MBIT:
        /* Packet configuration:
         * S1 size = 0 bits,
         * S0 size = 0 bytes,
         * 16-bit preamble.
         */
        packet_conf.plen = NRF_RADIO_PREAMBLE_LENGTH_16BIT;
        break;

    default:
        /* Packet configuration:
         * S1 size = 0 bits,
         * S0 size = 0 bytes,
         * 8-bit preamble.
         */
        packet_conf.plen = NRF_RADIO_PREAMBLE_LENGTH_8BIT;
        break;
    }

    nrf_radio_packet_configure(&packet_conf);
}

/**
 * @brief Function for configuring the radio to use a random address and a 254-byte random payload.
 * The S0 and S1 fields are not used.
 *
 * @param[in] mode Radio mode.
 * @param[in] pattern Radio transmission pattern.
 */
static void generate_modulated_rf_packet(nrf_radio_mode_t mode, transmit_pattern_t pattern) {
    radio_config(mode, pattern);

    /* One byte used for size, actual size is SIZE-1 */
#if USE_MORE_RADIO_MODES
    if (mode == NRF_RADIO_MODE_IEEE802154_250KBIT) {
        m_tx_packet[0] = IEEE_MAX_PAYLOAD_LEN - 1;
    } else {
        m_tx_packet[0] = sizeof(m_tx_packet) - 1;
    }
#else
    m_tx_packet[0] = sizeof(m_tx_packet) - 1;
#endif /* USE_MORE_RADIO_MODES */

    /* Fill payload with random data. */
    for (uint8_t i = 0; i < sizeof(m_tx_packet) - 1; i++) {
        if (pattern == TRANSMIT_PATTERN_RANDOM) {
            m_tx_packet[i + 1] = rnd8();
        } else if (pattern == TRANSMIT_PATTERN_11001100) {
            m_tx_packet[i + 1] = 0xCC;
        } else if (pattern == TRANSMIT_PATTERN_11110000) {
            m_tx_packet[i + 1] = 0xF0;
        } else {
            /* Do nothing. */
        }
    }

    nrf_radio_packetptr_set(m_tx_packet);
}

/**@brief Function for disabling radio.
 */
static void radio_disable(void) {
    nrf_radio_shorts_set(0);
    nrf_radio_int_disable(~0);
    nrf_radio_event_clear(NRF_RADIO_EVENT_DISABLED);

#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
    (void) nrf21540_power_down(NRF21540_EXECUTE_NOW, NRF21540_EXEC_MODE_BLOCKING);
#else
    nrf_radio_task_trigger(NRF_RADIO_TASK_DISABLE);
    while (!nrf_radio_event_check(NRF_RADIO_EVENT_DISABLED)) {
        /* Do nothing */
    }
#endif
    nrf_radio_event_clear(NRF_RADIO_EVENT_DISABLED);
}

static void radio_unmodulated_tx_carrier(nrf_radio_mode_t mode, nrf_radio_txpower_t txpower, uint8_t channel) {
    NRF_LOG_INFO("%s", __func__);
    radio_disable();

    nrf_radio_mode_set(mode);
#if !defined(NRF21540_DRIVER_ENABLE) || (NRF21540_DRIVER_ENABLE == 0)
    nrf_radio_shorts_enable(NRF_RADIO_SHORT_READY_START_MASK);
#endif
    nrf_radio_txpower_set(txpower);

    radio_channel_set(mode, channel);

#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
    (void) nrf21540_tx_set(NRF21540_EXECUTE_NOW, NRF21540_EXEC_MODE_NON_BLOCKING);
#else
    nrf_radio_task_trigger(NRF_RADIO_TASK_TXEN);
#endif
}

/**
 * @brief Function for starting the modulated TX carrier by repeatedly sending a packet with a random address and
 * a random payload.
 */
static void radio_modulated_tx_carrier(nrf_radio_mode_t mode, nrf_radio_txpower_t txpower, uint8_t channel, transmit_pattern_t pattern) {
    NRF_LOG_INFO("%s", __func__);
    radio_disable();
    generate_modulated_rf_packet(mode, pattern);

    switch (mode) {
#if USE_MORE_RADIO_MODES
    case NRF_RADIO_MODE_IEEE802154_250KBIT:
    case NRF_RADIO_MODE_BLE_LR125KBIT:
    case NRF_RADIO_MODE_BLE_LR500KBIT:
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
        nrf_radio_shorts_enable(NRF_RADIO_SHORT_PHYEND_START_MASK);
#else
        nrf_radio_shorts_enable(NRF_RADIO_SHORT_READY_START_MASK | NRF_RADIO_SHORT_PHYEND_START_MASK);
#endif
        break;
#endif /* USE_MORE_RADIO_MODES */

    case NRF_RADIO_MODE_BLE_1MBIT:
    case NRF_RADIO_MODE_BLE_2MBIT:
    case NRF_RADIO_MODE_NRF_1MBIT:
    case NRF_RADIO_MODE_NRF_2MBIT:
    default:
#ifdef NRF52832_XXAA
    case NRF_RADIO_MODE_NRF_250KBIT:
#endif /* NRF52832_XXAA */
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
        nrf_radio_shorts_enable(NRF_RADIO_SHORT_END_START_MASK);
#else
        nrf_radio_shorts_enable(NRF_RADIO_SHORT_READY_START_MASK | NRF_RADIO_SHORT_END_START_MASK);
#endif
        break;
    }

    nrf_radio_mode_set(mode);
    nrf_radio_txpower_set(txpower);

    radio_channel_set(mode, channel);

    m_tx_packet_cnt = 0;

    nrf_radio_event_clear(NRF_RADIO_EVENT_END);
    nrf_radio_int_enable(NRF_RADIO_INT_END_MASK);
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
    (void) nrf21540_tx_set(NRF21540_EXECUTE_NOW, NRF21540_EXEC_MODE_NON_BLOCKING);
#else
    nrf_radio_task_trigger(NRF_RADIO_TASK_TXEN);
#endif
    while (!nrf_radio_event_check(NRF_RADIO_EVENT_END)) {
        /* Do nothing */
    }
}

// static void radio_modulated_tx_carrier_duty_cycle(nrf_radio_mode_t mode, nrf_radio_txpower_t txpower, uint8_t channel, transmit_pattern_t pattern, uint32_t duty_cycle) {
//     // Lookup table with time per byte in each radio MODE
//     // Mapped per NRF_RADIO->MODE available on nRF5-series devices @ref <insert ref to mode register>
//     static const uint8_t time_in_us_per_byte[16] = { 8, 4, 32, 8, 4, 64, 16, 0, 0, 0, 0, 0, 0, 0, 0, 32 };
//     // 1 byte preamble, 5 byte address (BALEN + PREFIX), and sizeof(payload), no CRC
//     const uint32_t total_payload_size = 1 + 5 + sizeof(m_tx_packet);
//     const uint32_t total_time_per_payload = time_in_us_per_byte[mode] * total_payload_size;
//     // Duty cycle = 100 * Time_on / (time_on + time_off), we need to calculate "time_off" for delay.
//     // In addition, the timer includes the "total_time_per_payload", so we need to add this to the total timer cycle.
//     uint32_t delay_time = total_time_per_payload +
//                           ((100 * total_time_per_payload -
//                             (total_time_per_payload * duty_cycle)) /
//                            duty_cycle);

//     CRITICAL_REGION_ENTER();
//     radio_disable();
//     generate_modulated_rf_packet(mode, pattern);

//     nrf_radio_mode_set(mode);
//     nrf_radio_shorts_enable(NRF_RADIO_SHORT_READY_START_MASK |
//                             NRF_RADIO_SHORT_END_DISABLE_MASK);
//     nrf_radio_txpower_set(txpower);
//     radio_channel_set(mode, channel);

//     /* We let the TIMER start the radio transmission again. */
//     nrfx_timer_disable(&m_timer);
//     nrf_timer_shorts_disable(m_timer.p_reg, ~0);
//     nrf_timer_int_disable(m_timer.p_reg, ~0);

//     nrfx_timer_extended_compare(&m_timer,
//                                 NRF_TIMER_CC_CHANNEL1,
//                                 nrfx_timer_us_to_ticks(&m_timer, delay_time),
//                                 NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK,
//                                 true);

//     nrfx_timer_clear(&m_timer);
//     nrfx_timer_enable(&m_timer);
//     CRITICAL_REGION_EXIT();
// }

static void radio_rx(nrf_radio_mode_t mode, uint8_t channel, transmit_pattern_t pattern) {
    NRF_LOG_INFO("%s", __func__);
    radio_disable();

    nrf_radio_mode_set(mode);
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
    nrf_radio_shorts_enable(NRF_RADIO_SHORT_END_START_MASK);
#else
    nrf_radio_shorts_enable(NRF_RADIO_SHORT_READY_START_MASK |
                            NRF_RADIO_SHORT_END_START_MASK);
#endif
    nrf_radio_packetptr_set(m_rx_packet);

    radio_config(mode, pattern);
    radio_channel_set(mode, channel);

    m_rx_packet_cnt = 0;

    nrf_radio_int_enable(NRF_RADIO_INT_CRCOK_MASK);
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
    (void) nrf21540_rx_set(NRF21540_EXECUTE_NOW, NRF21540_EXEC_MODE_NON_BLOCKING);
#else
    nrf_radio_task_trigger(NRF_RADIO_TASK_RXEN);
#endif
}

// static void radio_sweep_start(uint8_t channel, uint32_t delay_ms) {
//     m_current_channel = channel;

//     nrfx_timer_disable(&m_timer);
//     nrf_timer_shorts_disable(m_timer.p_reg, ~0);
//     nrf_timer_int_disable(m_timer.p_reg, ~0);

//     nrfx_timer_extended_compare(&m_timer,
//                                 NRF_TIMER_CC_CHANNEL0,
//                                 nrfx_timer_ms_to_ticks(&m_timer, delay_ms),
//                                 NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
//                                 true);

//     nrfx_timer_clear(&m_timer);
//     nrfx_timer_enable(&m_timer);
// }

void radio_test__start(radio_test_config_t* p_config) {
    NRF_LOG_INFO("%s", __func__);
    switch (p_config->type) {
    case UNMODULATED_TX:
        radio_unmodulated_tx_carrier(p_config->mode, p_config->params.unmodulated_tx.txpower, p_config->params.unmodulated_tx.channel);
        break;
    case MODULATED_TX:
        radio_modulated_tx_carrier(p_config->mode, p_config->params.modulated_tx.txpower, p_config->params.modulated_tx.channel, p_config->params.modulated_tx.pattern);
        break;
    case RX:
        radio_rx(p_config->mode, p_config->params.rx.channel, p_config->params.rx.pattern);
        break;
    case TX_SWEEP:
        // radio_sweep_start(p_config->params.tx_sweep.channel_start, p_config->params.tx_sweep.delay_ms);
        break;
    case RX_SWEEP:
        // radio_sweep_start(p_config->params.rx_sweep.channel_start, p_config->params.rx_sweep.delay_ms);
        break;
    case MODULATED_TX_DUTY_CYCLE:
        // radio_modulated_tx_carrier_duty_cycle(p_config->mode, p_config->params.modulated_tx_duty_cycle.txpower, p_config->params.modulated_tx_duty_cycle.channel, p_config->params.modulated_tx_duty_cycle.pattern, p_config->params.modulated_tx_duty_cycle.duty_cycle);
        break;
    }
}

void radio_rx_stats_get(radio_rx_stats_t* p_rx_stats) {
    size_t size;

#if USE_MORE_RADIO_MODES
    nrf_radio_mode_t radio_mode;

    radio_mode = nrf_radio_mode_get();
    if (radio_mode == NRF_RADIO_MODE_IEEE802154_250KBIT) {
        size = IEEE_MAX_PAYLOAD_LEN;
    } else {
        size = sizeof(m_rx_packet);
    }
#else
    size = sizeof(m_rx_packet);
#endif /* USE_MORE_RADIO_MODES */

    p_rx_stats->last_packet.buf = m_rx_packet;
    p_rx_stats->last_packet.len = size;
    p_rx_stats->packet_cnt = m_rx_packet_cnt;
}

void toggle_dcdc_state(uint8_t dcdc_state) {
#ifdef NRF52840_XXAA
    if (dcdc_state == 0) {
        NRF_POWER->DCDCEN0 = (NRF_POWER->DCDCEN0 == POWER_DCDCEN0_DCDCEN_Disabled) ? 1 : 0;
    } else if (dcdc_state == 1) {
        NRF_POWER->DCDCEN = (NRF_POWER->DCDCEN == POWER_DCDCEN_DCDCEN_Disabled) ? 1 : 0;
    } else {
        // Do nothing.
    }
#else
    if (dcdc_state <= 1) {
        NRF_POWER->DCDCEN = dcdc_state;
    }
#endif // NRF52840_XXAA
}

/**
 * @brief Function for handling the Timer 0 interrupt used for the TX or RX sweep. The carrier is started with the new channel,
 * and the channel is incremented for the next interrupt.
 */
static void timer_handler(nrf_timer_event_t event_type, void* p_context) {
    const radio_test_config_t* p_config = (const radio_test_config_t*) p_context;

    if (event_type == NRF_TIMER_EVENT_COMPARE0) {
        uint8_t channel_start;
        uint8_t channel_end;

        if (p_config->type == TX_SWEEP) {
            radio_unmodulated_tx_carrier(p_config->mode,
                                         p_config->params.tx_sweep.txpower,
                                         m_current_channel);

            channel_start = p_config->params.tx_sweep.channel_start;
            channel_end = p_config->params.tx_sweep.channel_end;
        } else if (p_config->type == RX_SWEEP) {
            radio_rx(p_config->mode, m_current_channel, p_config->params.rx.pattern);

            channel_start = p_config->params.rx_sweep.channel_start;
            channel_end = p_config->params.rx_sweep.channel_end;
        } else {
            NRF_LOG_ERROR("Unexpected test type: %d\n", p_config->type);
            return;
        }

        m_current_channel++;
        if (m_current_channel > channel_end) {
            m_current_channel = channel_start;
        }
    }

    if (event_type == NRF_TIMER_EVENT_COMPARE1) {
#if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
        (void) nrf21540_tx_set(NRF21540_EXECUTE_NOW, NRF21540_EXEC_MODE_NON_BLOCKING);
#else
        nrf_radio_task_trigger(NRF_RADIO_TASK_TXEN);
#endif
    }
}

// static void timer_init(const radio_test_config_t* p_config) {
//     nrfx_err_t err;
//     nrfx_timer_config_t timer_cfg = {
//         .frequency = NRF_TIMER_FREQ_1MHz,
//         .mode = NRF_TIMER_MODE_TIMER,
//         .bit_width = NRF_TIMER_BIT_WIDTH_24,
//         .p_context = (void*) p_config,
// #if defined(NRF21540_DRIVER_ENABLE) && (NRF21540_DRIVER_ENABLE == 1)
//         // nRF21540 driver interrupts need to have higher interrupts priorities than interrupt which services nRF21540.
//         .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY + 1,
// #else
//         .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
// #endif
//     };

//     err = nrfx_timer_init(&m_timer, &timer_cfg, timer_handler);
//     if (err != NRFX_SUCCESS) {
//         NRF_LOG_ERROR("nrfx_timer_init failed with: %d\n", err);
//     }
// }

void RADIO_IRQHandler(void) {
    if (nrf_radio_event_check(NRF_RADIO_EVENT_CRCOK)) {
        nrf_radio_event_clear(NRF_RADIO_EVENT_CRCOK);
        m_rx_packet_cnt++;
    }

    if (nrf_radio_event_check(NRF_RADIO_EVENT_END)) {
        nrf_radio_event_clear(NRF_RADIO_EVENT_END);

        m_tx_packet_cnt++;
        if (m_tx_packet_cnt == m_p_test_config->params.modulated_tx.packets_num) {
            radio_disable();
            m_p_test_config->params.modulated_tx.cb();
        }
    }
}

void radio_test__cancel(void) {
    NRF_LOG_INFO("%s", __func__);
    // nrfx_timer_disable(&m_timer);
    radio_disable();
}

void radio_test__init(radio_test_config_t* p_config) {
    NRF_LOG_INFO("%s", __func__);
    if (!m_p_test_config) {
        nrf_rng_task_trigger(NRF_RNG_TASK_START);

#ifdef NVMC_ICACHECNF_CACHEEN_Msk
        nrf_nvmc_icache_config_set(NRF_NVMC, NRF_NVMC_ICACHE_ENABLE);
#endif // NVMC_ICACHECNF_CACHEEN_Msk

        // timer_init(p_config);

        NVIC_EnableIRQ(RADIO_IRQn);
        __enable_irq();

        m_p_test_config = p_config;
    }
}



// ============================================================
// radio_cmd_handler.c

#include "radio_cmd_handler.h"
#include <stdlib.h>
#include "nrf_cli.h"
#include "radio_test.h"

#define NRF_LOG_MODULE_NAME radio_cmd_handler
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();
#include "nrf_log_ctrl.h"

/** Indicates devices that support BLE LR and 802.15.4 radio modes. */
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(NRF52820_XXAA)
#define USE_MORE_NRF52_RADIO_POWER_OPTIONS 1
#else
#define USE_MORE_NRF52_RADIO_POWER_OPTIONS 0
#endif

#if defined(NRF52832_XXAA) || defined(NRF52833_XXAA)
#define TOGGLE_DCDC_HELP \
    "Toggle DCDC state <state>, if state = 1 then DC/DC converter is enabled"
#else
#define TOGGLE_DCDC_HELP                                          \
    "Toggle DCDC state <state>, "                                 \
    "if state = 1 then toggle DC/DC REG1 state, or if state = 0 " \
    "then toggle DC/DC REG0 state"
#endif

/**@brief Radio parameter configuration.
 */
typedef struct radio_param_config {
    transmit_pattern_t tx_pattern; /**< Radio transmission pattern. */
    nrf_radio_mode_t mode;         /**< Radio mode. Data rate and modulation. */
    nrf_radio_txpower_t txpower;   /**< Radio output power. */
    uint8_t channel_start;         /**< Radio start channel (frequency). */
    uint8_t channel_end;           /**< Radio end channel (frequency). */
    uint32_t delay_ms;             /**< Delay time in milliseconds. */
    uint32_t duty_cycle;           /**< Duty cycle. */
} radio_param_config_t;

static radio_test_config_t m_test_config; /**< Radio test configuration. */
static bool m_test_in_progress;           /**< If true, RX sweep, TX sweep or duty cycle test is performed. */
static radio_param_config_t m_config = {
    .tx_pattern = TRANSMIT_PATTERN_RANDOM,
    .mode = NRF_RADIO_MODE_BLE_1MBIT,
    .txpower = NRF_RADIO_TXPOWER_POS8DBM,
    .channel_start = 0,
    .channel_end = 80,
    .delay_ms = 10,
    .duty_cycle = 50,
};

void radio_cmd_handler__init(void) {
    NRF_LOG_INFO("%s", __func__);
    radio_test__init(&m_test_config);
}

#if USE_MORE_RADIO_MODES
static void ieee_channel_check(uint8_t channel) {
    if (m_config.mode == RADIO_MODE_MODE_Ieee802154_250Kbit) {
        if ((channel < IEEE_MIN_CHANNEL) || (channel > IEEE_MAX_CHANNEL)) {
            NRF_LOG_ERROR("For %s mode channel must be between %d and %d.", STRINGIFY_(RADIO_MODE_MODE_Ieee802154_250Kbit), IEEE_MIN_CHANNEL, IEEE_MAX_CHANNEL);
            NRF_LOG_INFO("Channel set to %d.", IEEE_MIN_CHANNEL);
        }
    }
}
#endif // USE_MORE_RADIO_MODES

radio_cmd_handler__ret_code_t radio_cmd_handler__stop(void) {
    NRF_LOG_INFO("%s", __func__);
    radio_test__cancel();
    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__rx_low_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 2;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = RX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.rx.channel = m_config.channel_start;
    m_test_config.params.rx.pattern = m_config.tx_pattern;

    radio_test__start(&m_test_config);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__rx_middle_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 42;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = RX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.rx.channel = m_config.channel_start;
    m_test_config.params.rx.pattern = m_config.tx_pattern;

    radio_test__start(&m_test_config);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__rx_high_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 80;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = RX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.rx.channel = m_config.channel_start;
    m_test_config.params.rx.pattern = m_config.tx_pattern;

    radio_test__start(&m_test_config);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_modulation_low_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 2;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = MODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.modulated_tx.txpower = m_config.txpower;
    m_test_config.params.modulated_tx.channel = m_config.channel_start;
    m_test_config.params.modulated_tx.pattern = m_config.tx_pattern;

    // if (argc == 2) {
    //     m_test_config.params.modulated_tx.packets_num = atoi(argv[1]);
    //     m_test_config.params.modulated_tx.cb = tx_modulated_carrier_end;
    // }

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the modulated TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_modulation_middle_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 42;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = MODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.modulated_tx.txpower = m_config.txpower;
    m_test_config.params.modulated_tx.channel = m_config.channel_start;
    m_test_config.params.modulated_tx.pattern = m_config.tx_pattern;

    // if (argc == 2) {
    //     m_test_config.params.modulated_tx.packets_num = atoi(argv[1]);
    //     m_test_config.params.modulated_tx.cb = tx_modulated_carrier_end;
    // }

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the modulated TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_modulation_high_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 80;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = MODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.modulated_tx.txpower = m_config.txpower;
    m_test_config.params.modulated_tx.channel = m_config.channel_start;
    m_test_config.params.modulated_tx.pattern = m_config.tx_pattern;

    // if (argc == 2) {
    //     m_test_config.params.modulated_tx.packets_num = atoi(argv[1]);
    //     m_test_config.params.modulated_tx.cb = tx_modulated_carrier_end;
    // }

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the modulated TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_unmodulation_low_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 2;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = UNMODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.unmodulated_tx.txpower = m_config.txpower;
    m_test_config.params.unmodulated_tx.channel = m_config.channel_start;

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_unmodulation_middle_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 42;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = UNMODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.unmodulated_tx.txpower = m_config.txpower;
    m_test_config.params.unmodulated_tx.channel = m_config.channel_start;

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}

radio_cmd_handler__ret_code_t radio_cmd_handler__tx_unmodulation_high_channel_start(void) {
    NRF_LOG_INFO("%s", __func__);
    if (m_test_in_progress) {
        radio_test__cancel();
        m_test_in_progress = false;
    }

    m_config.channel_start = 80;

#if USE_MORE_RADIO_MODES
    ieee_channel_check(m_config.channel_start);
#endif /* USE_MORE_RADIO_MODES */

    memset(&m_test_config, 0, sizeof(m_test_config));
    m_test_config.type = UNMODULATED_TX;
    m_test_config.mode = m_config.mode;
    m_test_config.params.unmodulated_tx.txpower = m_config.txpower;
    m_test_config.params.unmodulated_tx.channel = m_config.channel_start;

    radio_test__start(&m_test_config);

    NRF_LOG_INFO("%s. Start the TX carrier.", __func__);

    return RADIO_CMD_HANDLER__SUCCESS;
}


// ========================================================
main.c

#include <stdint.h>
#include <string.h>
#include "app_error.h"
#include "app_scheduler.h"
#include "app_timer.h"
#include "nordic_common.h"
#include "nrf.h"
#include "nrf_ble_lesc.h"
#include "nrf_delay.h"
#include "nrf_drv_clock.h"
#include "nrf_drv_wdt.h"
#include "nrf_fstorage.h"
#include "nrf_pwr_mgmt.h"

#define NRF_LOG_MODULE_NAME main
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "bcp_handler.h"
#include "ble_stack.h"
#include "board_led.h"
#include "internal_mem.h"
#include "piack_ble_handler.h"
#include "radio_cmd_handler.h"
#include "system_monitor.h"

static nrf_drv_wdt_channel_id m_channel_id;
APP_TIMER_DEF(s_wdt_feed_timer_id);
APP_TIMER_DEF(s_blink_led_timer_id);

static void led_blink_timer_handler(void* p_context) {
    NRF_LOG_DEBUG("%s.", __func__);
    static bool is_on = false;

    if (is_on) {
        board_led__set_power_on_led(BOARD_LED__TURN_OFF);
        is_on = false;
    } else {
        board_led__set_power_on_led(BOARD_LED__TURN_ON);
        is_on = true;
    }
}

static void wdt_event_handler(void) {
    NRF_LOG_INFO("watchdog timeout");
    NRF_LOG_FINAL_FLUSH();
    NVIC_SystemReset();
}

static void wdt_feed_event_timer_handler(void* p_context) {
    NRF_LOG_DEBUG("%s.", __func__);
}

static void wdt_init(void) {
    ret_code_t err_code;
    // NRF_DRV_WDT_DEAFULT_CONFIG
    nrf_drv_wdt_config_t config = {
        .behaviour = NRF_WDT_BEHAVIOUR_RUN_SLEEP_HALT,
        .reload_value = 5000,
        .interrupt_priority = NRFX_WDT_CONFIG_IRQ_PRIORITY,
    };

    // WDTドライバの初期化
    err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
    APP_ERROR_CHECK(err_code);

    // WDTチャンネルの割当て
    err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
    APP_ERROR_CHECK(err_code);

    // WDTの開始
    nrf_drv_wdt_enable();

    APP_ERROR_CHECK(app_timer_create(&s_wdt_feed_timer_id, APP_TIMER_MODE_REPEATED, wdt_feed_event_timer_handler));
    app_timer_start(s_wdt_feed_timer_id, APP_TIMER_TICKS(3000), NULL);
}

static void timers_init(void) {
    if (internal_mem__get_boot_mode() != INTERNAL_MEM__BOOT_MODE_NORMAL) {
        NRF_LOG_INFO("boot mode: RADIO_TEST, %d", internal_mem__get_boot_mode());
        ret_code_t err_code = nrf_drv_clock_init();
        APP_ERROR_CHECK(err_code);
        nrf_drv_clock_lfclk_request(NULL);
    }
    APP_ERROR_CHECK(app_timer_init());

    if (internal_mem__get_boot_mode() != INTERNAL_MEM__BOOT_MODE_NORMAL) {
        // Start 64 MHz crystal oscillator.
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
        NRF_CLOCK->TASKS_HFCLKSTART = 1;

        // Wait for the external oscillator to start up.
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
            // Do nothing.
        }
    }
}

static void log_init(void) {
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();
}

static void power_management_init(void) {
    APP_ERROR_CHECK(nrf_pwr_mgmt_init());
}

static void scheduler_init(void) {
    APP_SCHED_INIT(256, 48);
}

static void nrf_module_init(void) {
    log_init();
    internal_mem__init();
    timers_init();
    power_management_init();
    scheduler_init();
    // wdt_init();
}

static void my_module_init(void) {
    board_led__init();
    // board_led__set_power_on_led(BOARD_LED__TURN_ON);
    bcp_handler__init();

    APP_ERROR_CHECK(app_timer_create(&s_blink_led_timer_id, APP_TIMER_MODE_REPEATED, led_blink_timer_handler));
    if (internal_mem__get_boot_mode() != INTERNAL_MEM__BOOT_MODE_NORMAL) {
        NRF_LOG_INFO("boot mode: RADIO_TEST");
        radio_cmd_handler__init();
    } else {
        NRF_LOG_INFO("boot mode: NORMAL");
        piack_ble_handler__init();
    }
    system_monitor__init();
}

int main(void) {
    nrf_module_init();
    my_module_init();

#if 1
    switch (internal_mem__get_boot_mode()) {
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_LOW_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_LOW_CHANNEL");
        radio_cmd_handler__tx_unmodulation_low_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(50), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_MIDDLE_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_MIDDLE_CHANNEL");
        radio_cmd_handler__tx_unmodulation_middle_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(100), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_HIGH_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_UNMODULATION_HIGH_CHANNEL");
        radio_cmd_handler__tx_unmodulation_high_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(150), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_LOW_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_LOW_CHANNEL");
        radio_cmd_handler__tx_modulation_low_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(200), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_MIDDLE_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_MIDDLE_CHANNEL");
        radio_cmd_handler__tx_modulation_middle_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(250), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_HIGH_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_TX_MODULATION_HIGH_CHANNEL");
        radio_cmd_handler__tx_modulation_high_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(300), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_LOW_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_LOW_CHANNEL");
        radio_cmd_handler__rx_low_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(350), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_MIDDLE_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_MIDDLE_CHANNEL");
        radio_cmd_handler__rx_middle_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(400), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_HIGH_CHANNEL:
        NRF_LOG_INFO("INTERNAL_MEM__BOOT_MODE_RADIO_TEST_RX_HIGH_CHANNEL");
        radio_cmd_handler__rx_high_channel_start();
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(450), NULL);
        break;
    case INTERNAL_MEM__BOOT_MODE_NORMAL:
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(1000), NULL);
        // モジュール単体でのBLE動作確認用(PiACKに捕まえてもらう)
#if 0
        NRF_LOG_DEBUG("Bonding info exists: %d", ble_stack__bonding_info_exists());
        ble_stack__adv_start(false, NULL);
#endif
        break;
    default:
        app_timer_start(s_blink_led_timer_id, APP_TIMER_TICKS(5000), NULL);
        break;
    }
#endif

    NRF_LOG_INFO("Start main loop.");
    for (;;) {
        if (internal_mem__get_boot_mode() == INTERNAL_MEM__BOOT_MODE_NORMAL) {
            NRF_LOG_INFO("boot mode: NORMAL");
            ret_code_t err_code = nrf_ble_lesc_request_handler();
            if (err_code != NRF_ERROR_INVALID_STATE) {
                APP_ERROR_CHECK(err_code);
            }
        }
        app_sched_execute();
        if (NRF_LOG_PROCESS() == false) {
#if 0 //! @note 省電力モジュールと%E3%81%97て使用する場合は、有効にすること
            nrf_pwr_mgmt_run();
#endif
        }

        // NRF_LOG_FLUSH();
        // nrf_drv_wdt_channel_feed(m_channel_id);
    }
}

Parents
  • Additional information:

    The sample firmware is designed to use a CLI for changing modes. However, since we do not have a PC to run the CLI, we use nrf_fstorage to store the state. On startup, the firmware determines the mode from the stored state and transitions to the corresponding radio mode.

    The nRF52840 is connected to a GUI system running on a Raspberry Pi. Based on UI operations, commands are sent to rewrite the state in nrf_fstorage.
    (Since the GUI system and the nRF52840 are part of a single solution, adapting it to work with the CLI was not feasible.)

Reply
  • Additional information:

    The sample firmware is designed to use a CLI for changing modes. However, since we do not have a PC to run the CLI, we use nrf_fstorage to store the state. On startup, the firmware determines the mode from the stored state and transitions to the corresponding radio mode.

    The nRF52840 is connected to a GUI system running on a Raspberry Pi. Based on UI operations, commands are sent to rewrite the state in nrf_fstorage.
    (Since the GUI system and the nRF52840 are part of a single solution, adapting it to work with the CLI was not feasible.)

Children
No Data
Related