diff --git a/firmware/common/adc.c b/firmware/common/adc.c new file mode 100644 index 00000000..5d1ddaca --- /dev/null +++ b/firmware/common/adc.c @@ -0,0 +1,45 @@ +/* + * Copyright 2025 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "adc.h" +#include +#include + +uint16_t adc_read(uint8_t pin) +{ + bool alt_pin = (pin & 0x80); + pin &= ~0x80; + uint8_t pin_mask = (1 << pin); + if (alt_pin) { + SCU_ENAIO0 |= pin_mask; + } else { + SCU_ENAIO0 &= ~pin_mask; + } + ADC0_CR = ADC_CR_SEL(pin_mask) | ADC_CR_CLKDIV(45) | ADC_CR_PDN | ADC_CR_START(1); + while (!(ADC0_GDR & ADC_DR_DONE) || (((ADC0_GDR >> 24) & 0x7) != pin)) + ; + return (ADC0_GDR >> 6) & 0x03FF; +} + +void adc_off(void) +{ + ADC0_CR = 0; +} diff --git a/firmware/common/adc.h b/firmware/common/adc.h new file mode 100644 index 00000000..94481678 --- /dev/null +++ b/firmware/common/adc.h @@ -0,0 +1,30 @@ +/* + * Copyright 2025 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __ADC_H__ +#define __ADC_H__ + +#include + +uint16_t adc_read(uint8_t pin); +void adc_off(void); + +#endif // __ADC_H__ diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 58f3b17d..215c5528 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -39,12 +39,10 @@ #include "ice40_spi.h" #include "platform_detect.h" #include "clkin.h" -#include "selftest.h" #include #include #include #include -#include #if (defined HACKRF_ONE || defined PRALINE) #include "portapack.h" @@ -898,29 +896,6 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); -#ifndef RAD1O - /* Enable 32kHz oscillator */ - CREG_CREG0 &= ~(CREG_CREG0_PD32KHZ | CREG_CREG0_RESET32KHZ); - CREG_CREG0 |= CREG_CREG0_EN32KHZ; - - /* Allow 1ms to start up. */ - delay_us_at_mhz(1000, 204); - - /* Use frequency monitor to check 32kHz oscillator is running. */ - CGU_FREQ_MON = CGU_FREQ_MON_RCNT(511) | CGU_FREQ_MON_CLK_SEL(CGU_SRC_32K); - CGU_FREQ_MON |= CGU_FREQ_MON_MEAS_MASK; - while (CGU_FREQ_MON & CGU_FREQ_MON_MEAS_MASK) - ; - uint32_t count = - (CGU_FREQ_MON & CGU_FREQ_MON_FCNT_MASK) >> CGU_FREQ_MON_FCNT_SHIFT; - // We should see a single count, because 511 cycles of the 12MHz internal - // RC oscillator corresponds to 1.39 cycles of the 32768Hz clock. - selftest.rtc_osc_ok = (count == 1); - if (!selftest.rtc_osc_ok) { - selftest.report.pass = false; - } -#endif - #if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE) /* Disable unused clocks */ /* Start with PLLs */ @@ -954,7 +929,7 @@ void cpu_clock_init(void) CCU1_CLK_APB1_CAN1_CFG = 0; CCU1_CLK_APB1_I2S_CFG = 0; CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; - CCU1_CLK_APB3_ADC0_CFG = 0; + //CCU1_CLK_APB3_ADC0_CFG = 0; CCU1_CLK_APB3_ADC1_CFG = 0; CCU1_CLK_APB3_CAN0_CFG = 0; CCU1_CLK_APB3_DAC_CFG = 0; diff --git a/firmware/common/max2831.c b/firmware/common/max2831.c index d7bbbfc3..4215d792 100644 --- a/firmware/common/max2831.c +++ b/firmware/common/max2831.c @@ -32,6 +32,7 @@ #include "max2831.h" #include "max2831_regs.def" // private register def macros #include "selftest.h" +#include "adc.h" #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -66,26 +67,6 @@ static void max2831_init(max2831_driver_t* const drv) /* Write default register values to chip. */ max2831_regs_commit(drv); - - /* Disable lock detect output. */ - set_MAX2831_LOCK_DETECT_OUTPUT_EN(drv, false); - max2831_regs_commit(drv); - - // Read state of lock detect pin. - bool initial = gpio_read(drv->gpio_ld); - - // Enable lock detect output. - set_MAX2831_LOCK_DETECT_OUTPUT_EN(drv, true); - max2831_regs_commit(drv); - - // Read new state of lock detect pin. - bool new = gpio_read(drv->gpio_ld); - - // If the pin state changed, we know our writes are working. - selftest.max2831_ld_test_ok = initial != new; - if (!selftest.max2831_ld_test_ok) { - selftest.report.pass = false; - } } /* @@ -181,6 +162,34 @@ void max2831_start(max2831_driver_t* const drv) { max2831_regs_commit(drv); max2831_set_mode(drv, MAX2831_MODE_STANDBY); + + /* Read RSSI with ADC. */ + uint16_t rssi_1 = selftest.max2831_mux_rssi_1 = adc_read(1); + + /* Switch to temperature sensor. */ + set_MAX2831_RSSI_MUX(drv, MAX2831_RSSI_MUX_TEMP); + max2831_regs_commit(drv); + + /* Read temperature. */ + uint16_t temp = selftest.max2831_mux_temp = adc_read(1); + + /* Switch back to RSSI. */ + set_MAX2831_RSSI_MUX(drv, MAX2831_RSSI_MUX_RSSI); + max2831_regs_commit(drv); + + /* Read RSSI again. */ + uint16_t rssi_2 = selftest.max2831_mux_rssi_2 = adc_read(1); + + /* If the ADC results are as expected, we know our writes are working. */ + bool rssi_1_good = (rssi_1 < 10); + bool rssi_2_good = (rssi_2 < 10); + bool temp_good = (temp > 100) && (temp < 500); // -40 to +85C + + selftest.max2831_mux_test_ok = rssi_1_good & rssi_2_good & temp_good; + + if (!selftest.max2831_mux_test_ok) { + selftest.report.pass = false; + } } void max2831_tx(max2831_driver_t* const drv) diff --git a/firmware/common/platform_detect.c b/firmware/common/platform_detect.c index 73656479..ec00a3b8 100644 --- a/firmware/common/platform_detect.c +++ b/firmware/common/platform_detect.c @@ -23,9 +23,9 @@ #include "firmware_info.h" #include "gpio_lpc.h" #include "hackrf_core.h" +#include "adc.h" #include -#include static board_id_t platform = BOARD_ID_UNDETECTED; static board_rev_t revision = BOARD_REV_UNDETECTED; @@ -64,23 +64,6 @@ static struct gpio_t gpio_led1 = GPIO(2, 1); static struct gpio_t gpio_led2 = GPIO(2, 2); static struct gpio_t gpio_led3 = GPIO(2, 8); -/* - * Return 10-bit ADC result. - */ -uint16_t adc_read(uint8_t pin) -{ - pin &= 0x7; - uint8_t pin_mask = (1 << pin); - ADC0_CR = ADC_CR_SEL(pin_mask) | ADC_CR_CLKDIV(45) | ADC_CR_PDN | ADC_CR_START(1); - while (!(ADC0_GDR & ADC_DR_DONE) || (((ADC0_GDR >> 24) & 0x7) != pin)) {} - return (ADC0_GDR >> 6) & 0x03FF; -} - -void adc_off(void) -{ - ADC0_CR = 0; -} - /* * Starting with r6, HackRF One has pin straps on ADC pins that indicate * hardware revision. Those pins were unconnected prior to r6, so we test for diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index b755db64..8079e465 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -71,7 +71,7 @@ static const uint16_t rffc5071_regs_default[RFFC5071_NUM_REGS] = { 0x0000, /* 1A */ 0x0000, /* 1B */ 0xc840, /* 1C */ - 0x1000, /* 1D */ + 0x0000, /* 1D, readsel = 0b0000 */ 0x0005, /* 1E */}; @@ -81,13 +81,13 @@ void rffc5071_init(rffc5071_driver_t* const drv) memcpy(drv->regs, rffc5071_regs_default, sizeof(drv->regs)); drv->regs_dirty = 0x7fffffff; - selftest.mixer_id = rffc5071_reg_read(drv, RFFC5071_READBACK_REG); - if ((selftest.mixer_id >> 3) != 2031) { - selftest.report.pass = false; - } - /* Write default register values to chip. */ rffc5071_regs_commit(drv); + + selftest.mixer_id = rffc5071_reg_read(drv, RFFC5071_READBACK_REG); + if ((selftest.mixer_id >> 3) != 4416) { + selftest.report.pass = false; + } } /* diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 0731801b..f083de3c 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -28,7 +28,10 @@ typedef struct { uint16_t mixer_id; #ifdef PRALINE - bool max2831_ld_test_ok; + uint16_t max2831_mux_rssi_1; + uint16_t max2831_mux_temp; + uint16_t max2831_mux_rssi_2; + bool max2831_mux_test_ok; #else uint16_t max283x_readback_bad_value; uint16_t max283x_readback_expected_value; @@ -37,9 +40,6 @@ typedef struct { #endif uint8_t si5351_rev_id; bool si5351_readback_ok; -#ifndef RAD1O - bool rtc_osc_ok; -#endif #ifdef PRALINE bool sgpio_rx_ok; bool xcvr_loopback_ok; diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 06cb7bec..a620310b 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -191,6 +191,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/radio.c ${PATH_HACKRF_FIRMWARE_COMMON}/selftest.c ${PATH_HACKRF_FIRMWARE_COMMON}/m0_state.c + ${PATH_HACKRF_FIRMWARE_COMMON}/adc.c ) if(BOARD STREQUAL "RAD1O") diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index f4b06d8e..9f6d3cc5 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -62,6 +62,7 @@ set(SRC_M4 usb_api_sweep.c usb_api_selftest.c usb_api_ui.c + usb_api_adc.c "${PATH_HACKRF_FIRMWARE_COMMON}/usb_queue.c" "${PATH_HACKRF_FIRMWARE_COMMON}/fault_handler.c" "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_jtag.c" diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index d9717864..a8adf401 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -48,6 +48,7 @@ #include "usb_api_operacake.h" #include "usb_api_praline.h" #include "usb_api_selftest.h" +#include "usb_api_adc.h" #include "operacake.h" #include "usb_api_sweep.h" #include "usb_api_transceiver.h" @@ -148,6 +149,7 @@ static usb_request_handler_fn vendor_request_handler[] = { NULL, #endif usb_vendor_request_read_selftest, + usb_vendor_request_adc_read, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_adc.c b/firmware/hackrf_usb/usb_api_adc.c new file mode 100644 index 00000000..ee1bc98e --- /dev/null +++ b/firmware/hackrf_usb/usb_api_adc.c @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "adc.h" +#include "usb_api_adc.h" + +usb_request_status_t usb_vendor_request_adc_read( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + if ((endpoint->setup.index & ~0x80) > 7) { + return USB_REQUEST_STATUS_STALL; + } + uint16_t value = adc_read(endpoint->setup.index); + adc_off(); + endpoint->buffer[0] = value & 0xff; + endpoint->buffer[1] = value >> 8; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 2, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_OK; +} diff --git a/firmware/hackrf_usb/usb_api_adc.h b/firmware/hackrf_usb/usb_api_adc.h new file mode 100644 index 00000000..8f4d2568 --- /dev/null +++ b/firmware/hackrf_usb/usb_api_adc.h @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __USB_API_ADC_H__ +#define __USB_API_ADC_H__ + +#include +#include + +usb_request_status_t usb_vendor_request_adc_read( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +#endif // __USB_API_ADC_H__ diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 71cdb415..29cbc8a1 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -22,7 +22,6 @@ #include #include #include -#include #include "usb_api_selftest.h" #include "selftest.h" #include "platform_detect.h" @@ -74,8 +73,8 @@ void generate_selftest_report(void) append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); #ifdef PRALINE - append(&s, &c, "Transceiver: MAX2831, LD pin test: "); - append(&s, &c, selftest.max2831_ld_test_ok ? "PASS" : "FAIL"); + append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); + append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); #else append(&s, &c, "Transceiver: "); @@ -95,11 +94,6 @@ void generate_selftest_report(void) } append(&s, &c, "\n"); #endif -#ifndef RAD1O - append(&s, &c, "32kHz oscillator: "); - append(&s, &c, selftest.rtc_osc_ok ? "PASS" : "FAIL"); - append(&s, &c, "\n"); -#endif #ifdef PRALINE append(&s, &c, "SGPIO RX test: "); append(&s, &c, selftest.sgpio_rx_ok ? "PASS" : "FAIL"); diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index b95b2478..b1977f8d 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -577,6 +577,7 @@ static void usage() printf("\t-u, --ui <1/0>: enable/disable UI\n"); printf("\t-l, --leds : configure LED state (0 for all off, 1 for default)\n"); printf("\t-t, --selftest: read self-test report\n"); + printf("\t-a, --adc : read value from an ADC channel. Add 0x80 for alternate pin\n"); printf("\nExamples:\n"); printf("\thackrf_debug --si5351c -n 0 -r # reads from si5351c register 0\n"); printf("\thackrf_debug --si5351c -c # displays si5351c multisynth configuration\n"); @@ -608,6 +609,7 @@ static struct option long_options[] = { {"ui", required_argument, 0, 'u'}, {"leds", required_argument, 0, 'l'}, {"selftest", no_argument, 0, 't'}, + {"adc", required_argument, 0, 'a'}, {0, 0, 0, 0}, }; @@ -636,6 +638,7 @@ int main(int argc, char** argv) uint32_t clkin_state; uint32_t narrowband_state; uint32_t bitstream_index; + uint32_t adc_channel; bool set_tx_limit = false; bool set_rx_limit = false; bool set_p1 = false; @@ -644,6 +647,7 @@ int main(int argc, char** argv) bool set_narrowband = false; bool set_fpga_bitstream = false; bool read_selftest = false; + bool read_adc = false; int result = hackrf_init(); if (result) { @@ -656,7 +660,7 @@ int main(int argc, char** argv) while ((opt = getopt_long( argc, argv, - "n:rw:d:cmsfg1:2:C:N:P:ST:R:h?u:l:t", + "n:rw:d:cmsfg1:2:C:N:P:ST:R:h?u:l:ta:", long_options, &option_index)) != EOF) { switch (opt) { @@ -764,6 +768,11 @@ int main(int argc, char** argv) read_selftest = true; break; + case 'a': + read_adc = true; + result = parse_int(optarg, &adc_channel); + break; + case 'h': case '?': usage(); @@ -803,7 +812,7 @@ int main(int argc, char** argv) if (!(write || read || dump_config || dump_state || set_tx_limit || set_rx_limit || set_ui || set_leds || set_p1 || set_p2 || set_clkin || - set_narrowband || set_fpga_bitstream || read_selftest)) { + set_narrowband || set_fpga_bitstream || read_selftest || read_adc)) { fprintf(stderr, "Specify read, write, or config option.\n"); usage(); return EXIT_FAILURE; @@ -811,7 +820,7 @@ int main(int argc, char** argv) if (part == PART_NONE && !set_ui && !dump_state && !set_tx_limit && !set_rx_limit && !set_leds && !set_p1 && !set_p2 && !set_clkin && - !set_narrowband && !set_fpga_bitstream && !read_selftest) { + !set_narrowband && !set_fpga_bitstream && !read_selftest && !read_adc) { fprintf(stderr, "Specify a part to read, write, or print config from.\n"); usage(); return EXIT_FAILURE; @@ -964,6 +973,21 @@ int main(int argc, char** argv) printf("%s", selftest.msg); } + if (read_adc) { + uint16_t value; + result = hackrf_read_adc(device, adc_channel, &value); + if (result != HACKRF_SUCCESS) { + printf("hackrf_read_adc() failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + printf("ADC0_%d (%s pin): %d\n", + adc_channel & 0x7, + adc_channel & 0x80 ? "alternate" : "dedicated", + value); + } + result = hackrf_close(device); if (result) { printf("hackrf_close() failed: %s (%d)\n", diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index eab9b0e0..77f055a5 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -109,6 +109,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_SET_FPGA_BITSTREAM = 54, HACKRF_VENDOR_REQUEST_CLKIN_CTRL = 55, HACKRF_VENDOR_REQUEST_READ_SELFTEST = 56, + HACKRF_VENDOR_REQUEST_READ_ADC = 57, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -1244,6 +1245,35 @@ int ADDCALL hackrf_read_selftest(hackrf_device* device, hackrf_selftest* selftes } } +int ADDCALL hackrf_read_adc(hackrf_device* device, uint8_t adc_channel, uint16_t* value) +{ + USB_API_REQUIRED(device, 0x0109); + + int result; + + if ((adc_channel & ~0x80) > 7) { + return HACKRF_ERROR_INVALID_PARAM; + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_READ_ADC, + 0, + adc_channel, + (unsigned char*) value, + 2, + 0); + + if (result < 2) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + *value = FROM_LE16(*value); + return HACKRF_SUCCESS; + } +} + int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* state) { USB_API_REQUIRED(device, 0x0106) diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 0ffa048f..10c51ee4 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -1354,6 +1354,20 @@ extern ADDAPI int ADDCALL hackrf_read_selftest( hackrf_device* device, hackrf_selftest* value); +/** + * Read a value from an ADC channel + * + * @param[in] device device to query + * @param[in] adc_channel ADC channel, e.g. 0 for ADC0_0. Add 0x80 to use an alternate pin. + * @param[out] value Value read from ADC. + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup debug + */ +extern ADDAPI int ADDCALL hackrf_read_adc( + hackrf_device* device, + uint8_t adc_channel, + uint16_t* value); + /** * Set transmit underrun limit *