Merge pull request #1608 from greatscottgadgets/fix-selftests

Fix self-tests
This commit is contained in:
Michael Ossmann
2025-11-25 21:03:58 -05:00
committed by GitHub
16 changed files with 274 additions and 85 deletions

45
firmware/common/adc.c Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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 <libopencm3/lpc43xx/adc.h>
#include <libopencm3/lpc43xx/scu.h>
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;
}

30
firmware/common/adc.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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 <stdint.h>
uint16_t adc_read(uint8_t pin);
void adc_off(void);
#endif // __ADC_H__

View File

@@ -39,12 +39,10 @@
#include "ice40_spi.h"
#include "platform_detect.h"
#include "clkin.h"
#include "selftest.h"
#include <libopencm3/lpc43xx/cgu.h>
#include <libopencm3/lpc43xx/ccu.h>
#include <libopencm3/lpc43xx/scu.h>
#include <libopencm3/lpc43xx/ssp.h>
#include <libopencm3/lpc43xx/creg.h>
#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;

View File

@@ -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)

View File

@@ -23,9 +23,9 @@
#include "firmware_info.h"
#include "gpio_lpc.h"
#include "hackrf_core.h"
#include "adc.h"
#include <libopencm3/lpc43xx/scu.h>
#include <libopencm3/lpc43xx/adc.h>
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

View File

@@ -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;
}
}
/*

View File

@@ -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;

View File

@@ -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")

View File

@@ -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"

View File

@@ -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 =

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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 <stddef.h>
#include <usb_queue.h>
#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;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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 <usb_type.h>
#include <usb_request.h>
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__

View File

@@ -22,7 +22,6 @@
#include <stdio.h>
#include <stddef.h>
#include <usb_queue.h>
#include <libopencm3/lpc43xx/cgu.h>
#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");

View File

@@ -577,6 +577,7 @@ static void usage()
printf("\t-u, --ui <1/0>: enable/disable UI\n");
printf("\t-l, --leds <state>: configure LED state (0 for all off, 1 for default)\n");
printf("\t-t, --selftest: read self-test report\n");
printf("\t-a, --adc <channel>: 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",

View File

@@ -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)

View File

@@ -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
*