diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 2dafe72cc..0688c91ea 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,7 +13,7 @@ build: # Build documentation in the "docs/" directory with Sphinx sphinx: - configuration: docs/source/conf.py + configuration: docs_htime/source/conf.py # Build PDF for docs formats: diff --git a/Readme.md b/Readme.md index 45c27954f..ffb4c31cd 100644 --- a/Readme.md +++ b/Readme.md @@ -1,38 +1,43 @@ -# HackRF +# HTime -This repository contains hardware designs and software for HackRF, -a low cost, open source Software Defined Radio platform. +HTime is a firmware extension for the HackRF One, the open-hardware/open-source +SDR by [Great Scott Gadgets](https://www.greatscottgadgets.com/hackrf/one/). +HTime provides the HackRF SDR with the +capability to perform timed commands and a precise phase synchronization +of internal clocks (sampling, MCU, output) for the best timing performance. +HTime is developed by Fabrizio Pollastri. -![HackRF One](https://raw.github.com/mossmann/hackrf/master/docs/images/HackRF-One-fd0-0009.jpeg) +## Features -(photo by fd0 from https://github.com/fd0/hackrf-one-pictures) +* Unix-like time scale, with a resolution of 5 ns +* RX/TX sampling synchronized with the time scale +* Fine time scale/frequency adjust for synchronization with remote reference + radio signals with a resolution of about 0.25 Hz @ 10 MHz +* No external hardware required +* Host time tool for access to time extension API -principal author: Michael Ossmann +For now, HTime works only with a 10 MHz sample rate. -Information on HackRF and purchasing HackRF: https://greatscottgadgets.com/hackrf/ +## Build and Install --------------------- +HTime is developed into a fork of the original HackRF repository, so all +build and install rules for the original HackRF software apply also to +HTime. Please, see the documentation at + -# Documentation +## Documentation -Documentation for HackRF can be viewed on [Read the Docs](https://hackrf.readthedocs.io/en/latest/). The raw documentation files for HackRF are in the [docs folder](https://github.com/mossmann/hackrf/tree/master/docs) in this repository and can be built locally by installing [Sphinx Docs](https://www.sphinx-doc.org/en/master/usage/installation.html) and running `make html`. Documentation changes can be submitted through pull request and suggestions can be made as GitHub issues. +* [HTime — Working Principle](docs_htime/htime.md) +* [HTime USB API](docs_htime/htime_api.md) +* [HTime Tool](docs_htime/htime_tool.md) -To create a PDF of the HackRF documentation from the HackRF repository while on Ubuntu: -* run `sudo apt install latexmk texlive-latex-extra` -* navigate to hackrf/docs on command line -* run the command `make latex` -* run the command `make latexpdf` +All other documentation about the HackRF can be found +[here](https://hackrf.readthedocs.io/en/latest/). --------------------- +## License -# Getting Help +HTime © 2025 documentation by Fabrizio Pollastri is licensed under CC BY-SA 4.0 -Before asking for help with HackRF, check to see if your question is listed in the [FAQ](https://hackrf.readthedocs.io/en/latest/faq.html). +HTime © 2025 software by Fabrizio Pollastri is licensed under GNU GPL v2.0 -For assistance with HackRF general use or development, please look at the [issues on the GitHub project](https://github.com/greatscottgadgets/hackrf/issues). This is the preferred place to ask questions so that others may locate the answer to your question in the future. -We invite you to join our community discussions on [Discord](https://discord.gg/rsfMw3rsU8). Note that while technical support requests are welcome here, we do not have support staff on duty at all times. Be sure to also submit an issue on GitHub if you've found a bug or if you want to ensure that your request will be tracked and not overlooked. - -If you wish to see past discussions and questions about HackRF, you may also view the [mailing list archives](https://pairlist9.pair.net/pipermail/hackrf-dev/). - -GitHub issues on this repository that are labelled "technical support" by Great Scott Gadgets employees can expect a response time of two weeks. We currently do not have expected response times for other GitHub issues or pull requests for this repository. diff --git a/docs_htime/htime.md b/docs_htime/htime.md new file mode 100644 index 000000000..c24f3f1f4 --- /dev/null +++ b/docs_htime/htime.md @@ -0,0 +1,142 @@ +# HTime — Working Principle + +## Working Principle + +The block diagram below shows the HTime extension implemented in both +the firmware and some spare hardware resources. + +![HTime extension](images/time_extension.webp) + +The time scale is generated starting from a spare timer (timer 3) of the +MCU that is configured to operate as a ticks counter at 200 MHz, from the +MCU clock. + +Every second, the ticks counter reaches the max count of 200e6 and +generates an interrupt and restarts counting from zero. A service routine, +associated with this event, increments a software counter counting the +seconds. + +The ticks counter is also used to generate a trigger signal to determine +a precise time for the start of data sampling. This is obtained loading +into one of the 4 match registers of the timer, a trigger delay in ticks +from the start of the last second. When the counter reaches this value, an +output pin goes to 1 level, while the counter continues counting up the +max count (200e6 ticks). The trigger is reset from the ticks counter +service routine at the start of the next second. + +The ticks counter is also programmed to generate a pulse on an output pin +every time a max count is reached. This pulse can be used as a 1PPS output +signal for external usage. + +### Clock generation + +As explained above, all signals controlling the sampling timing are +generated from the MCU clock. Since these signals interact with the logic +driven by the sampling clock, it is essential that these clocks have a +constant integer frequency ratio. + +For this purpose, the firmware was modified to configure the clock +generator to produce the clocks sampling, MCU, and output from the same +source, to eliminate possible phase shifts and frequency drifts among +them. + +This is truly possible with HackRF board revisions different from R9 that +have the si5351c multisynthesizer with 8 clock sections, implementing a +little creative routing of the clocks on the board. +Revision board R9 has the si5351a multisynthesizer with 3 clock sections +only. In this case, two clock sections are used: section 1 for the +sampling clock and section 2 for the MCU and output clocks. + +The clock generation for the tuner and the mixer is left unchanged. + +## HackRF board revisions not R9 + +HackRF board versions with the SI5351C multisynthesizer with 8 clock +sections. + +### Clock routing + +A first choice was to configure the MCU to change its clock source from +the ordinary 12 MHz XTAL to the MCU input pin GP_CLKIN. + +Unfortunately, not r9 boards connect to the GP_CLKIN input to the output +section 7 of the SI5351C. The PLL divisor of this section is limited to a +small range of even integer values (6-254), so it is not suitable for a +fine frequency control-like sections 0-5 offering a fractional divisor +with 20 bits resolution. + +Fortunately, the MCU allows the input external clock (GP_CLKIN) to be +mapped alternatively on pin 120 (PF_4), that is connected to connector +P22 pin 12 (signal i2s0_rx_sck). So, it is possible to connect a jumper +from this to connector p22 pin1 (signal clkout) and use the clkout clock +as input clock for the MCU. + +![HackRF Clock Generation not R9 boards](images/hackrf_clock_gen_not_r9.webp) + +![Output clock jumper for not R9 board revisions](images/hackrf_clkout_jumper.png) + +The SI5351C allows to configure the block of sections 0-3 to be all +driven by the PLL frequency of section 0. So, the phase and frequencies +generated by sections 0-3 are coherent and cannot drift. Since sections +0, 1, and 2 generate the clocks needed for data sampling, in this way +also the MCU clock becomes coherent with the sampling clock, and the +requirement of the previous section is met. + +Since clkout comes from section 3 of the si5351c, and this section can be +configured to take as input clock the output of section 0 a full +resolution divisor (20 bits), suitable for the clock frequency adjustments +required by GSCV frequency control. + +### Trigger routing + +The trigger is generated by the match register 0 of the ticks counter. +This event is configured to change the level of the MCU output P2_3, +pin 87 (signal i2c1_sda) that is connected to connector p22, pin 6. +The HackRF board already has a trigger input on connector p28, pin 16. +When this input goes to 1 level, it enables the data sampling. +So, the generated trigger can be easily routed with a wire jumper +to the sampling trigger input, to do the job. + +![Sampling trigger jumper for all board revisions](images/hackrf_trigger_jumper.png) + +## HackRF board revision R9 + +HackRF board version with SI5351A multisynthesizer with 3 clock sections. + +> **Note — Best synchronization results are obtained with HackRF One +> board revision R9.** +> On R9, the sampling clock and the MCU clock are both generated by a +> single section of the SI5351A PLL synthesizer, so all clocks share the +> same phase reference and no inter-clock phase jumps can occur. On all +> other board revisions, two separate sections of PLL synthesizers are +> involved (SI5351C): one drives the sampling clock and another drives +> the MCU clock. Because these two PLLs are independent, a random phase +> jump between the two clocks may occasionally occur — approximately +> 100 ns at a 10 MHz sampling rate — causing a transient timing error in +> the synchronization. + +### Clock routing + +Also for revision R9, the choice was to configure the MCU to change its +clock source from the ordinary 12 MHz XTAL to the MCU input pin GP_CLKIN. + +In this board version, GP_CLKIN is already connected to the clkout from +section 2 of the multisynthesizer. No jumpers are needed. While the +multisynthesizer section 1 generates the sampling clocks. These +frequencies are adjusted by software updating the parameters in +multisynthesizer sections 1 and 2. Since these updates cannot be +simultaneous, a small phase shift is added at each change. To minimize +this effect, the change order is reversed after each change. + +The clock generation for the tuner and the mixer from the +multisynthesizer section 0 is left unchanged. + +![HackRF Clock Generation R9 boards](images/hackrf_clock_gen_r9.webp) + +### Trigger routing + +The trigger routing is the same as the other HackRF board revisions. + +--- + +

HTime documentation by Fabrizio Pollastri is licensed under CC BY-SA 4.0

diff --git a/docs_htime/htime_api.md b/docs_htime/htime_api.md new file mode 100644 index 000000000..1911176ad --- /dev/null +++ b/docs_htime/htime_api.md @@ -0,0 +1,114 @@ +# HTime USB API + +This API allows access to all the firmware features of HTime. An example +of application can be seen in the +[HTime Tool source](../host/hackrf-tools/src/hackrf_time.c) and its +[usage documentation](htime_tool.md). + +--- + +**`hackrf_time_set_divisor_next_pps(device, divisor)`** + +Set **divisor** value (uint32_t) into the ticks counter at the next PPS. +With the HackRF MCU working @ 200 MHz, the ticks counter must be set to +200000000-1 to obtain a counting period of 1 second. **device** is a +pointer to the HackRF device. + +--- + +**`hackrf_time_set_divisor_one_pps(device, divisor)`** + +Set **divisor** value (uint32_t) into the ticks counter from the next PPS +for only one counter cycle then restore the previous divisor value. +**device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_set_trig_delay_next_pps(device, trig_delay)`** + +Set **trig_delay** value (uint32_t) as the sampling trigger delay at next +PPS. The trigger delay is from the start of the second (PPS leading edge) +in tick units. **device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_get_seconds_now(device, &seconds)`** + +Get the value of the second counter immediately into **seconds** +(* int64_t). **device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_set_seconds_now(device, seconds)`** + +Set the **seconds** value (int64_t) immediately into the second counter. +**device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_set_seconds_next_pps(device, seconds)`** + +Set the **seconds** value (int64_t) at next PPS into the second counter. +**device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_get_ticks_now(device, &ticks)`** + +Get the value of the tick counter immediately into **ticks** +(* uint32_t). **device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_set_ticks_now(device, ticks)`** + +Set the **ticks** value (uint32_t) immediately into the tick counter. +**device** is a pointer to the HackRF device. + +--- + +**`hackrf_time_set_clk_freq(device, clk_freq)`** + +Set the **clk_freq** value (double) as the sampling rate synchronized to +the MCU clock. Must be a value near (+-100 Hz) 10 MHz. **device** is a +pointer to the HackRF device. + +--- + +**`hackrf_time_set_mcu_clk_sync(device, enable)`** + +Set the **enable** value (uint8_t) of the MCU synchronized clock mode. +When this mode is on, all relevant clocks are kept in sync to avoid phase +shifts and frequency drifts. **device** is a pointer to the HackRF +device. + +--- + +**`hackrf_time_set_trig_hold_enable_next_pps(device, enable)`** + +Set the requested trigger hold state with PPS-synchronous behavior. +**device** is a pointer to the HackRF device. + +Timing semantics: + +- **enable = 1**: sampled at the next PPS leading edge and becomes active + one PPS later. +- **enable = 0**: becomes active at the next PPS leading edge. + +When trigger hold is active, trigger trailing edge action is suppressed, +so the trigger output can stay active across the second boundary +(long capture crossing second border). When trigger hold is inactive, +trigger trailing edge is generated as usual. + +--- + +**`hackrf_time_set_pps_out_enable_next_pps(device, enable)`** + +Set the **enable** value (uint8_t) of PPS output at the next PPS leading +edge. When enabled, regular PPS output pulses are generated. +When disabled, PPS output is kept low (inactive). **device** is a pointer +to the HackRF device. + +--- + +

HTime documentation by Fabrizio Pollastri is licensed under CC BY-SA 4.0

diff --git a/docs_htime/htime_tool.md b/docs_htime/htime_tool.md new file mode 100644 index 000000000..24bd1fb1b --- /dev/null +++ b/docs_htime/htime_tool.md @@ -0,0 +1,46 @@ +# HTime Tool + +The HTime Tool is a host application for accessing the API of the HTime +extension. + +## Tool help + +``` +Manage HTime for HackRF: ticks, seconds, A/D trigger, clocks. + +Usage: + -h, --help: this help + -d, --divisor : set divisor to val next pps + -D, --Divisor : set divisor to val for one pps + -f, --set_clk_freq : set sync clock freq to + -k, --ticks : read ticks counter now + -K, --Ticks : set ticks counter now + -l, --trig_hold <0/1>: set trig hold state (1: sample next PPS, active one PPS later; 0: active next PPS) + -p, --pps_out <0/1>: enable/disable pps output on next pps + -r, --trig_delay : set trig delay to val next pps + -s, --seconds : set seconds counter now to value + -S, --Seconds : as -s but in sync to next pps + -t, --get_seconds: read seconds counter now + -y, --mcu_clk_sync <0/1>: enable/disable mcu clock sync + +Examples: + hackrf_time -s 1234 # set seconds counter to 1234 now + hackrf_time -l 1 # enable trigger hold on next PPS + hackrf_time -p 0 # disable PPS output on next PPS + hackrf_time -y 1 # enable MCU sync mode + +v0.1.0 20250215 F.P. +``` + +Each tool option corresponds to one API function of HTime. For a more +detailed explanation of each function, see +[HTime USB API](htime_api.md). + +## Source + +The tool source code is at +[host/hackrf-tools/src/hackrf_time.c](../host/hackrf-tools/src/hackrf_time.c). + +--- + +

HTime documentation by Fabrizio Pollastri is licensed under CC BY-SA 4.0

diff --git a/docs_htime/images/hackrf_clkout_jumper.png b/docs_htime/images/hackrf_clkout_jumper.png new file mode 100644 index 000000000..8a88b798a Binary files /dev/null and b/docs_htime/images/hackrf_clkout_jumper.png differ diff --git a/docs_htime/images/hackrf_clock_gen_not_r9.webp b/docs_htime/images/hackrf_clock_gen_not_r9.webp new file mode 100644 index 000000000..8386f9354 Binary files /dev/null and b/docs_htime/images/hackrf_clock_gen_not_r9.webp differ diff --git a/docs_htime/images/hackrf_clock_gen_r9.webp b/docs_htime/images/hackrf_clock_gen_r9.webp new file mode 100644 index 000000000..ac2398ef0 Binary files /dev/null and b/docs_htime/images/hackrf_clock_gen_r9.webp differ diff --git a/docs_htime/images/hackrf_trigger_jumper.png b/docs_htime/images/hackrf_trigger_jumper.png new file mode 100644 index 000000000..96aec1821 Binary files /dev/null and b/docs_htime/images/hackrf_trigger_jumper.png differ diff --git a/docs_htime/images/time_extension.webp b/docs_htime/images/time_extension.webp new file mode 100644 index 000000000..bd8d010d7 Binary files /dev/null and b/docs_htime/images/time_extension.webp differ diff --git a/firmware/common/clkin.c b/firmware/common/clkin.c index 5ab8123a6..8db6ba281 100644 --- a/firmware/common/clkin.c +++ b/firmware/common/clkin.c @@ -73,9 +73,9 @@ void clkin_detect_init(void) reset.first_tcr = TIMER_TCR_CEN | TIMER_TCR_CRST; reset.second_tcr = TIMER_TCR_CEN; - timer_dma_lli.src = (uint32_t) & (reset); - timer_dma_lli.dest = (uint32_t) & (TIMER2_TCR); - timer_dma_lli.next_lli = (uint32_t) & (timer_dma_lli); + timer_dma_lli.src = (uint32_t) &(reset); + timer_dma_lli.dest = (uint32_t) &(TIMER2_TCR); + timer_dma_lli.next_lli = (uint32_t) &(timer_dma_lli); timer_dma_lli.control = GPDMA_CCONTROL_TRANSFERSIZE(2) | GPDMA_CCONTROL_SBSIZE(0) // 1 | GPDMA_CCONTROL_DBSIZE(0) // 1 diff --git a/firmware/common/gpio_lpc.h b/firmware/common/gpio_lpc.h index f51ffd2bf..10a0faa75 100644 --- a/firmware/common/gpio_lpc.h +++ b/firmware/common/gpio_lpc.h @@ -45,7 +45,7 @@ typedef struct gpio_port_t { uint32_t _reserved4[31]; volatile uint32_t clr; /* +0x280 */ uint32_t _reserved5[31]; - volatile uint32_t not ; /* +0x300 */ + volatile uint32_t not; /* +0x300 */ } gpio_port_t; struct gpio_t { @@ -60,9 +60,10 @@ struct gpio_t { #define GPIO_LPC_PORT_OFFSET (0x2000) #define GPIO_LPC_PORT(_n) \ - ((gpio_port_t*) ((GPIO_LPC_BASE + GPIO_LPC_PORT_OFFSET) + (_n) *4)) -#define GPIO_LPC_W(_port_num, _pin_num) \ - (volatile uint32_t*) ((GPIO_LPC_BASE + GPIO_LPC_W_OFFSET) + ((_port_num) *0x80) + ((_pin_num) *4)) + ((gpio_port_t*) ((GPIO_LPC_BASE + GPIO_LPC_PORT_OFFSET) + (_n) * 4)) +#define GPIO_LPC_W(_port_num, _pin_num) \ + (volatile uint32_t*) ((GPIO_LPC_BASE + GPIO_LPC_W_OFFSET) + \ + ((_port_num) * 0x80) + ((_pin_num) * 4)) // clang-format off #define GPIO(_port_num, _pin_num) { \ diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index cedff0f3d..041e39204 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -2,6 +2,7 @@ * Copyright 2012-2022 Great Scott Gadgets * Copyright 2012 Jared Boone * Copyright 2013 Benjamin Vernoux + * Copyright 2025 Fabrizio Pollastri * * This file is part of HackRF. * @@ -438,7 +439,7 @@ bool sample_rate_set(const uint32_t sample_rate_hz) { uint32_t p1 = 4608; uint32_t p2 = 0; - uint32_t p3 = 0; + uint32_t p3 = 1; switch (sample_rate_hz) { case 8000000: @@ -539,7 +540,7 @@ Configure PLL1 (Main MCU Clock) to max speed (204MHz). Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. This function shall be called after cpu_clock_init(). */ -static void cpu_clock_pll1_max_speed(void) +void cpu_clock_pll1_max_speed(uint8_t clock_source, uint8_t msel) { uint32_t reg_val; @@ -552,7 +553,7 @@ static void cpu_clock_pll1_max_speed(void) reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1); CGU_BASE_M4_CLK = reg_val; - /* 2. Enable the crystal oscillator. */ + /* 2. if required, enable the crystal oscillator. */ CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK; /* 3. Wait 250us. */ @@ -573,12 +574,12 @@ static void cpu_clock_pll1_max_speed(void) CGU_PLL1_CTRL_PSEL_MASK | CGU_PLL1_CTRL_MSEL_MASK | CGU_PLL1_CTRL_NSEL_MASK ); - /* Set PLL1 up to 12MHz * 17 = 204MHz. + /* Set PLL1 up to 12MHz * 17 = 204MHz or 10Mhz * 20 = 200MHz. * Direct mode: FCLKOUT = FCCO = M*(FCLKIN/N) */ - reg_val |= CGU_PLL1_CTRL_CLK_SEL(CGU_SRC_XTAL) | + reg_val |= CGU_PLL1_CTRL_CLK_SEL(clock_source) | CGU_PLL1_CTRL_PSEL(0) | CGU_PLL1_CTRL_NSEL(0) | - CGU_PLL1_CTRL_MSEL(16) | + CGU_PLL1_CTRL_MSEL(msel-1) | CGU_PLL1_CTRL_FBSEL(0) | CGU_PLL1_CTRL_DIRECT(1); // clang-format on @@ -605,7 +606,7 @@ static void cpu_clock_pll1_max_speed(void) /* clock startup for LPC4320 configure PLL1 to max speed (204MHz). Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. */ -void cpu_clock_init(void) +void cpu_clock_init(uint8_t clock_source, uint8_t msel) { /* use IRC as clock source for APB1 (including I2C0) */ CGU_BASE_APB1_CLK = CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_IRC); @@ -626,17 +627,17 @@ void cpu_clock_init(void) /* * Clocks on HackRF One r9: - * CLK0 -> MAX5864/CPLD/SGPIO (sample clocks) - * CLK1 -> RFFC5072/MAX2839 + * CLK0 -> RFFC5072/MAX2839 (40 MHz tuner) + * CLK1 -> MAX5864/CPLD/SGPIO (sample clock x 2) * CLK2 -> External Clock Output/LPC43xx (power down at boot) * * Clocks on other platforms: - * CLK0 -> MAX5864/CPLD - * CLK1 -> CPLD - * CLK2 -> SGPIO + * CLK0 -> MAX5864/CPLD (sample clock) + * CLK1 -> CPLD (sample clock x 2) + * CLK2 -> SGPIO (sample clock x 2) * CLK3 -> External Clock Output (power down at boot) - * CLK4 -> RFFC5072 (MAX2837 on rad1o) - * CLK5 -> MAX2837 (MAX2871 on rad1o) + * CLK4 -> RFFC5072 (MAX2837 on rad1o) (40 MHz tuner) + * CLK5 -> MAX2837 (MAX2871 on rad1o) (40 MHz tuner) * CLK6 -> none * CLK7 -> LPC43xx (uses a 12MHz crystal by default) */ @@ -695,7 +696,7 @@ void cpu_clock_init(void) /* set xtal oscillator to low frequency mode */ CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_HF_MASK; - cpu_clock_pll1_max_speed(); + cpu_clock_pll1_max_speed(clock_source, msel); /* use XTAL_OSC as clock source for APB1 */ CGU_BASE_APB1_CLK = @@ -794,7 +795,7 @@ void cpu_clock_init(void) CCU1_CLK_M4_TIMER0_CFG = 0; //CCU1_CLK_M4_TIMER1_CFG = 0; //CCU1_CLK_M4_TIMER2_CFG = 0; - CCU1_CLK_M4_TIMER3_CFG = 0; + //CCU1_CLK_M4_TIMER3_CFG = 0; CCU1_CLK_M4_UART1_CFG = 0; CCU1_CLK_M4_USART0_CFG = 0; CCU1_CLK_M4_USART2_CFG = 0; @@ -879,6 +880,14 @@ void pin_setup(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ + + /* configure pin as TIMER 3 MATCH 0 output: 1pps out */ + scu_pinmux(SCU_PINMUX_PPS1_OUT, SCU_GPIO_PDN | SCU_CONF_FUNCTION6); + /* configure pin as TIMER 3 MATCH 1 output: sampling trigger out */ + scu_pinmux(SCU_PINMUX_SAMP_TRIGGER, SCU_GPIO_PDN | SCU_CONF_FUNCTION6); + /* configure pin as TIMER 3 CAPTURE 0 input: 1pps input */ + scu_pinmux(SCU_PINMUX_PPS1_IN, SCU_GPIO_PDN | SCU_CONF_FUNCTION6); + #ifdef HACKRF_ONE scu_pinmux(SCU_PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(SCU_PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); @@ -964,7 +973,10 @@ void pin_setup(void) rf_path_pin_setup(&rf_path); /* Configure external clock in */ - scu_pinmux(SCU_PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); + if (detected_platform() == BOARD_ID_HACKRF1_R9) + scu_pinmux(SCU_PINMUX_GP_CLKIN_R9, SCU_CLK_IN | SCU_CONF_FUNCTION1); + else + scu_pinmux(SCU_PINMUX_GP_CLKIN_NOTR9, SCU_CLK_IN | SCU_CONF_FUNCTION1); sgpio_configure_pin_functions(&sgpio_config); } diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index d6211b379..da3a268e7 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -2,6 +2,7 @@ * Copyright 2012-2022 Great Scott Gadgets * Copyright 2012 Benjamin Vernoux * Copyright 2012 Jared Boone + * Copyright 2025 Fabrizio Pollastri * * This file is part of HackRF. * @@ -63,8 +64,13 @@ extern "C" { #define SCU_PINMUX_BOOT2 (P2_8) /* GPIO5[7] on P2_8 */ #define SCU_PINMUX_BOOT3 (P2_9) /* GPIO1[10] on P2_9 */ #endif -#define SCU_PINMUX_PP_LCD_TE (P2_3) /* GPIO5[3] on P2_3 */ -#define SCU_PINMUX_PP_LCD_RDX (P2_4) /* GPIO5[4] on P2_4 */ + +#define SCU_PINMUX_PP_LCD_TE (P2_3) /* GPIO5[3] on P2_3 */ +#define SCU_PINMUX_PPS1_OUT (P2_3) /* T3_MAT0 on P2_3, cpu pin 87 */ + +#define SCU_PINMUX_PP_LCD_RDX (P2_4) /* GPIO5[4] on P2_4 */ +#define SCU_PINMUX_SAMP_TRIGGER (P2_4) /* T3_MAT1 on P2_4, cpu pin 88 */ + #define SCU_PINMUX_PP_UNUSED (P2_8) /* GPIO5[7] on P2_8 */ #define SCU_PINMUX_PP_LCD_WRX (P2_9) /* GPIO1[10] on P2_9 */ #define SCU_PINMUX_PP_DIR (P2_13) /* GPIO1[13] on P2_13 */ @@ -224,13 +230,16 @@ extern "C" { #define SCU_PINMUX_SD_CD (P1_13) /* GPIO1[6] */ #define SCU_PINMUX_PP_IO_STBX (P2_0) /* GPIO5[0] */ +#define SCU_PINMUX_PPS1_IN (P2_0) /* T3_CAP0 on P2_0, cpu pin 75 */ + #define SCU_PINMUX_PP_ADDR (P2_1) /* GPIO5[1] */ #define SCU_PINMUX_U0_TXD (P2_0) /* GPIO5[0] */ #define SCU_PINMUX_U0_RXD (P2_1) /* GPIO5[1] */ #define SCU_PINMUX_ISP (P2_7) /* GPIO0[7] */ -#define SCU_PINMUX_GP_CLKIN (P4_7) +#define SCU_PINMUX_GP_CLKIN_R9 (P4_7) +#define SCU_PINMUX_GP_CLKIN_NOTR9 (PF_4) /* HackRF One r9 */ #define SCU_H1R9_CLKIN_EN (P6_7) /* GPIO5[15] on P6_7 */ @@ -280,7 +289,7 @@ extern rf_path_t rf_path; extern jtag_t jtag_cpld; extern i2c_bus_t i2c0; -void cpu_clock_init(void); +void cpu_clock_init(uint8_t clock_source, uint8_t msel); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); @@ -316,6 +325,8 @@ void hw_sync_enable(const hw_sync_mode_t hw_sync_mode); void halt_and_flash(const uint32_t duration); +void cpu_clock_pll1_max_speed(uint8_t clock_source, uint8_t msel); + #ifdef __cplusplus } #endif diff --git a/firmware/common/rad1o/render.c b/firmware/common/rad1o/render.c index 9f2bb4258..252c9d5ca 100644 --- a/firmware/common/rad1o/render.c +++ b/firmware/common/rad1o/render.c @@ -108,7 +108,7 @@ int rad1o_DoChar(int sx, int sy, int c) } while (0); -#define xy_(x, y) ((x < 0 || y < 0 || x >= RESX || y >= RESY) ? 0 : (y) *RESX + (x)) +#define xy_(x, y) ((x < 0 || y < 0 || x >= RESX || y >= RESY) ? 0 : (y) * RESX + (x)) #define gPx(x, y) (data[x * height + (height - y / 8 - 1)] & (1 << (y % 8))) int x = 0; diff --git a/firmware/common/rom_iap.c b/firmware/common/rom_iap.c index c926359ae..992b93d1f 100644 --- a/firmware/common/rom_iap.c +++ b/firmware/common/rom_iap.c @@ -36,7 +36,7 @@ typedef void (*IAP_t)(uint32_t[], uint32_t[]); typedef struct { const IAP_t IAP; /* If equal to 0x12345678 IAP not implemented */ /* Other TBD */ -} * pENTRY_ROM_API_t; +}* pENTRY_ROM_API_t; #define pROM_API ((pENTRY_ROM_API_t) ROM_IAP_ADDR) diff --git a/firmware/common/sct.h b/firmware/common/sct.h index 61c43199b..8d4445801 100644 --- a/firmware/common/sct.h +++ b/firmware/common/sct.h @@ -2310,7 +2310,7 @@ EV[0:15]_CTRL[MATCHMEM, DIRECTION] /* -- SCT_OUTn_SETm: A 1 in bit m selects event m to set output n (or clear it * if SETCLRn = 0x1 or 0x2) event 0 = bit 0, event 1 = bit 1,..., event 15 = * bit 15. */ -#define SCT_OUTn_SETm(n, m) (((n) &1) << (m)) +#define SCT_OUTn_SETm(n, m) (((n) & 1) << (m)) /* --- SCT_OUT0_CLR -------------------------------------- */ #define SCT_OUT0_CLR MMIO32(SCT_BASE + 0x504) @@ -2363,4 +2363,4 @@ EV[0:15]_CTRL[MATCHMEM, DIRECTION] /* -- SCT_OUTn_CLR_CLRm: A 1 in bit m selects event m to clear output n (or set * it if SETCLRn = 0x1 or 0x2) event 0 = bit 0, event 1 = bit 1,..., event 15 = * bit 15. */ -#define SCT_OUTn_CLRm(n, m) (((n) &1) << (m)) +#define SCT_OUTn_CLRm(n, m) (((n) & 1) << (m)) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index af8588758..470df3a7a 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -1,6 +1,7 @@ /* * Copyright 2012-2022 Great Scott Gadgets * Copyright 2012 Jared Boone + * Copyright 2025 Fabrizio Pollastri * * This file is part of HackRF. * @@ -40,6 +41,9 @@ static enum pll_sources active_clock_source = PLL_SOURCE_UNINITIALIZED; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; +/* mcu clock from si5351c syntetizer */ +static bool mcu_clk_sync = false; + /* write to single register */ void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val) { @@ -221,8 +225,14 @@ void si5351c_configure_clock_control( #endif if (clkout_enabled) { clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); + + if (mcu_clk_sync && detected_platform() != BOARD_ID_HACKRF1_R9) { + clkout_ctrl |= SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4); + si5351c_configure_multisynth(drv, 3, 80 * 128 - 512, 0, 0, 1); + } else { + clkout_ctrl |= SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF); + } } else { clkout_ctrl = SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE; } @@ -231,7 +241,7 @@ void si5351c_configure_clock_control( /* External clock output is kept in current state */ uint8_t data[] = { 16, - SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(pll) | + SI5351C_CLK_PLL_SRC(pll) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA), SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) | @@ -245,24 +255,34 @@ void si5351c_configure_clock_control( SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA) | SI5351C_CLK_INV, SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(pll) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA), SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE, /* not connected, but: PLL A int mode */ SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; + if (mcu_clk_sync) { + data[1] |= SI5351C_CLK_INT_MODE; + data[6] |= SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4); + } else { + data[1] |= SI5351C_CLK_FRAC_MODE; + data[6] |= SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF); + } if (detected_platform() == BOARD_ID_HACKRF1_R9) { data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC_A | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC_A | + data[2] = SI5351C_CLK_PLL_SRC_A | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); data[3] = clkout_ctrl; data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; + if (mcu_clk_sync) + data[2] |= SI5351C_CLK_INT_MODE; + else + data[2] |= SI5351C_CLK_FRAC_MODE; } si5351c_write(drv, data, sizeof(data)); } @@ -331,10 +351,10 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source si5351c_configure_clock_control(drv, source); active_clock_source = source; if (detected_platform() == BOARD_ID_HACKRF1_R9) { - /* 25MHz XTAL * (0x0e00+512)/128 = 800mhz -> int mode */ + /* 25MHz XTAL * (0x0e00+512)/128 = 800 MHz -> int mode */ uint8_t pll_data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00}; if (source == PLL_SOURCE_CLKIN) { - /* 10MHz CLKIN * (0x2600+512)/128 = 800mhz */ + /* 10MHz CLKIN * (0x2600+512)/128 = 800 MHz */ pll_data[4] = 0x26; } si5351c_write(drv, pll_data, sizeof(pll_data)); @@ -362,6 +382,7 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable) if (detected_platform() == BOARD_ID_HACKRF1_R9) { clkout = 2; } + /* Configure clock to 10MHz */ si5351c_configure_multisynth(drv, clkout, 80 * 128 - 512, 0, 1, 0); @@ -369,6 +390,25 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable) si5351c_enable_clock_outputs(drv); } +void si5351c_mcu_clk_enable(uint8_t enable) +{ + /* only for R9 boards */ + if (detected_platform() != BOARD_ID_HACKRF1_R9) + return; + + if (enable) + gpio_set(&gpio_h1r9_mcu_clk_en); + else + gpio_clear(&gpio_h1r9_mcu_clk_en); +} + +void si5351c_mcu_clk_sync(si5351c_driver_t* const drv, uint8_t enable) +{ + mcu_clk_sync = (enable > 0); + + si5351c_clkout_enable(drv, enable); +} + void si5351c_init(si5351c_driver_t* const drv) { if (detected_platform() == BOARD_ID_HACKRF1_R9) { diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 493cc73be..e972f56fd 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -1,6 +1,7 @@ /* * Copyright 2012-2022 Great Scott Gadgets * Copyright 2012 Jared Boone + * Copyright 2025 Fabrizio Pollastri * * This file is part of HackRF. * @@ -102,6 +103,8 @@ void si5351c_write( const uint8_t* const data, const size_t data_count); void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable); +void si5351c_mcu_clk_enable(uint8_t enable); +void si5351c_mcu_clk_sync(si5351c_driver_t* const drv, uint8_t enable); void si5351c_init(si5351c_driver_t* const drv); #ifdef __cplusplus diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 8312c12bd..1eadaf58a 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -272,8 +272,8 @@ static void rad1o_ui_set_bb_tx_vga_gain(const uint32_t gain_db) ui_update(); } -static void rad1o_ui_set_first_if_frequency(const uint64_t frequency - __attribute__((unused))) +static void rad1o_ui_set_first_if_frequency( + const uint64_t frequency __attribute__((unused))) { // Not implemented } diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 311c18ff9..40e59dc1f 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -50,6 +50,7 @@ set(SRC_M4 usb_api_operacake.c usb_api_sweep.c usb_api_ui.c + usb_api_time.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 e6536795e..87f694e25 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -2,6 +2,7 @@ * Copyright 2012-2022 Great Scott Gadgets * Copyright 2012 Jared Boone * Copyright 2013 Benjamin Vernoux + * Copyright 2025 Fabrizio Pollastri * * This file is part of HackRF. * @@ -52,6 +53,9 @@ #include "usb_api_ui.h" #include "usb_bulk_buffer.h" #include "usb_api_m0_state.h" + +#include "usb_api_time.h" + #include "cpld_xc2c.h" #include "portapack.h" #include "hackrf_ui.h" @@ -126,7 +130,22 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_supported_platform, usb_vendor_request_set_leds, usb_vendor_request_user_config_set_bias_t_opts, -}; + + usb_vendor_request_time_set_divisor_next_pps, + usb_vendor_request_time_set_divisor_one_pps, + usb_vendor_request_time_set_trig_delay_next_pps, + usb_vendor_request_time_get_seconds_now, + usb_vendor_request_time_set_seconds_now, + usb_vendor_request_time_set_seconds_next_pps, + usb_vendor_request_time_get_ticks_now, + usb_vendor_request_time_set_ticks_now, + usb_vendor_request_time_set_clk_freq, + usb_vendor_request_time_set_mcu_clk_sync, + usb_vendor_request_time_set_trig_hold_enable_next_pps, + usb_vendor_request_time_set_pps_out_enable_next_pps, + usb_vendor_request_time_get_pps_in_active, + + NULL}; static const uint32_t vendor_request_handler_count = sizeof(vendor_request_handler) / sizeof(vendor_request_handler[0]); @@ -242,7 +261,7 @@ int main(void) #if (defined HACKRF_ONE || defined RAD1O) enable_rf_power(); #endif - cpu_clock_init(); + cpu_clock_init(CGU_SRC_XTAL, 17); /* Wake the M0 */ ipc_halt_m0(); @@ -295,6 +314,9 @@ int main(void) clkin_detect_init(); } + /* start time timer */ + time_timer_init(); + while (true) { transceiver_request_t request; diff --git a/firmware/hackrf_usb/usb_api_time.c b/firmware/hackrf_usb/usb_api_time.c new file mode 100644 index 000000000..8554fd4a4 --- /dev/null +++ b/firmware/hackrf_usb/usb_api_time.c @@ -0,0 +1,621 @@ +/* + * Copyright 2025 Fabrizio Pollastri + * + * 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 +//#include + +#include +#include +//#include + +//#include "gpdma.h" +#include +//#include +//#include +#include +#include +#include +#include "../common/hackrf_core.h" +#include "hackrf_ui.h" +#include "platform_detect.h" +#include "si5351c.h" + +#include "usb_api_time.h" + +// @204 MHz CPU clock, divisor for 1Hz +#define PPS1_CLK_INIT_DIVISOR 204000000 - 1 +// @200 MHz CPU clock +#define PPS1_WIDTH 4000 // 20 us PPS out pulse width +#define TRIG_INIT_DELAY 40000000 // 200 ms trigger delay +#define TRIG_DELAY_DISABLE 0xFFFFFFFF +#define TRIG_HOLD_INVALID 0xFF + +int64_t seconds; +int64_t new_seconds; +uint32_t new_divisor; +uint32_t one_pps_divisor; +uint32_t current_divisor; +uint32_t new_trig_delay; +uint32_t current_trig_delay; +uint32_t pps_in_phase; +uint32_t pps_period_count; +uint32_t last_pps_in_period; +uint8_t new_trig_hold_enable; +uint8_t current_trig_hold_enable; +uint8_t pps_out_enable; +uint8_t pps_in_active; + + +/**** time timer functions ****/ + +void time_timer_init(void) +{ + /* no prescaler */ + timer_set_prescaler(TIMER3, 0); + + /* count cpu clock cycles */ + timer_set_mode(TIMER3, TIMER_CTCR_MODE_TIMER); + + /* reset counter and do PPS interrupt when match register 0 (MR0). + * Also enable MR1 interrupt to schedule trigger trailing edge in ISR. */ + TIMER3_MCR = TIMER_MCR_MR0R | TIMER_MCR_MR0I | TIMER_MCR_MR1I; + + /* PPS input to capture register 0: rising edge and interrupt */ + TIMER3_CCR = TIMER_CCR_CAP0RE | TIMER_CCR_CAP0I; + + /* PPS leading edge: at next MR0 match, set to 1 the external output. + * PPS output is enabled (regular PPS period) */ + pps_out_enable = 1; + TIMER3_EMR = (TIMER_EMR_EMC_SET << TIMER_EMR_EMC0_SHIFT); + + /* set proper divisor (1Hz) to match MR0 */ + TIMER3_MR0 = PPS1_CLK_INIT_DIVISOR; + current_divisor = PPS1_CLK_INIT_DIVISOR; + + /* sampling trigger leading edge: at next R1 match, set to 1 the + * external output. Trigger on holding disabled (intra second capture) */ + TIMER3_EMR |= (TIMER_EMR_EMC_SET << TIMER_EMR_EMC1_SHIFT); + + /* set init trigger delay to match MR1 */ + TIMER3_MR1 = TRIG_DELAY_DISABLE; + current_trig_delay = TRIG_DELAY_DISABLE; + + /* prevent TIMER3_MR3 from interfering with SCT */ + CREG_CREG6 |= CREG_CREG6_CTOUTCTRL; + + /* clear second counter */ + seconds = 0; + + /* clear new values variables */ + new_divisor = 0; + one_pps_divisor = 0; + new_seconds = 0; + new_trig_delay = 0; + new_trig_hold_enable = TRIG_HOLD_INVALID; + current_trig_hold_enable = 0; + pps_period_count = 0; + last_pps_in_period = 0; + pps_in_active = 0; + + /* enable PPS interrupt */ + nvic_enable_irq(NVIC_TIMER3_IRQ); + + /* start counting */ + timer_reset(TIMER3); + timer_enable_counter(TIMER3); +} + +void timer3_isr() +{ + /* Process trigger state machine on MR1 interrupt. + * Leading edge configuration is set in the MR0/PPS flow. + * Here we schedule the trailing edge at PPS leading edge, + * unless trigger hold is enabled. */ + if (TIMER3_IR & TIMER_IR_MR1INT) { + uint32_t emc1_mode = (TIMER3_EMR >> TIMER_EMR_EMC1_SHIFT) & 0x3; + + /* Only act after a leading-edge match (EMC1 was SET). */ + if (emc1_mode == TIMER_EMR_EMC_SET) { + + /* no interrupt at trailing edge, managed by PPS interupt */ + TIMER3_MCR &= ~TIMER_MCR_MR1I; + + /* if periodic trigger ... */ + if (!current_trig_hold_enable) { + + /* trigger trailing edge at PPS leading edge */ + TIMER3_MR1 = TIMER3_MR0; + + /* clear trigger at next MR1 match */ + TIMER3_EMR &= ~(0x3 << TIMER_EMR_EMC1_SHIFT); + TIMER3_EMR |= (TIMER_EMR_EMC_CLEAR << TIMER_EMR_EMC1_SHIFT); + + } else { + + /* Hold active: suppress trailing edge action. */ + TIMER3_EMR &= ~(0x3 << TIMER_EMR_EMC1_SHIFT); + TIMER3_EMR |= (TIMER_EMR_EMC_NOTHING << TIMER_EMR_EMC1_SHIFT); + } + } + + /* clear pending match-1 interrupt */ + TIMER3_IR |= TIMER_IR_MR1INT; + } + + /* process input PPS phase measure only on CAP0 interrupt */ + if (TIMER3_IR & TIMER_IR_CR0INT) { + + /* get input PPS phase with respect to internal PPS */ + pps_in_phase = TIMER3_CR0; + + /* small phases cause interrupt cycles too short for timer3_isr, + so add one full cycle to phase */ + if (pps_in_phase < 2 * PPS1_WIDTH) { + pps_in_phase += current_divisor; + } + + /* If phase is not zero, align internal PPS by setting next + one pps divisor to this phase */ + one_pps_divisor = pps_in_phase; + + /* input PPS has been recently observed */ + last_pps_in_period = pps_period_count; + pps_in_active = 1; + + /* clear pending capture-0 interrupt */ + TIMER3_IR |= TIMER_IR_CR0INT; + } + + /* Process PPS state machine only on MR0 interrupt */ + if (TIMER3_IR & TIMER_IR_MR0INT) { + + /* if start of PPS pulse: PPS is set (leading edge) and match + * MR0 is set to count until to next PPS trailing edge. */ + if (TIMER3_MR0 != PPS1_WIDTH) { + pps_period_count++; + + /* consider PPS input inactive after two full local PPS periods + * without capture events */ + if (pps_in_active && ((pps_period_count - last_pps_in_period) > 2)){ + pps_in_active = 0; + } + + /* second counter */ + seconds++; + + /* if requested, set new current divisor. */ + if (new_divisor) { + current_divisor = new_divisor; + new_divisor = 0; + } + + /* if requested, update seconds in sync with the new second. */ + if (new_seconds) { + seconds = new_seconds; + new_seconds = 0; + } + + /* if requested, update new trig delay. */ + if (new_trig_delay) { + current_trig_delay = new_trig_delay; + new_trig_delay = 0; + } + + /* sample trigger hold at PPS boundary. */ + if (new_trig_hold_enable != TRIG_HOLD_INVALID) { + current_trig_hold_enable = new_trig_hold_enable; + new_trig_hold_enable = TRIG_HOLD_INVALID; + } + + /* trigger logic (PPS part) + * prepare leading edge if not disable or hold status */ + if (current_trig_delay != TRIG_DELAY_DISABLE + && !current_trig_hold_enable) { + + /* restore current trig delay */ + TIMER3_MR1 = current_trig_delay; + + /* trigger leading edge: at next R1 match, + * set to 1 the external output. */ + TIMER3_EMR &= ~(0x3 << TIMER_EMR_EMC1_SHIFT); + TIMER3_EMR |= (TIMER_EMR_EMC_SET << TIMER_EMR_EMC1_SHIFT); + + /* enable trigger leading edge interrupt */ + TIMER3_MCR |= TIMER_MCR_MR1I; + } + + /* set R0 match value to keep PPS high for the given time */ + TIMER3_MR0 = PPS1_WIDTH; + + /* PPS trailing edge: at next MR0 match, clear the external + * output. */ + TIMER3_EMR &= ~(0x3 << TIMER_EMR_EMC0_SHIFT); + TIMER3_EMR |= (TIMER_EMR_EMC_CLEAR << TIMER_EMR_EMC0_SHIFT); + + /* do not reset counter at next R0 match: count must continue + * for one second period. */ + TIMER3_MCR &= ~TIMER_MCR_MR0R; + + } + + /* else is the end of PPS pulse: match R0 is set to + * count until the next PPS leading edge. */ + else { + + /* if requested, set one PPS divisor for one PPS cycle + * then restore current divisor. */ + if (one_pps_divisor) { + TIMER3_MR0 = one_pps_divisor; + one_pps_divisor = 0; + } + /* otherwise, restore current divisor. */ + else + TIMER3_MR0 = current_divisor; + + /* PPS leading edge: at next R0 match (start of second), set to 1 + * the external output if PPS output is enabled (regular PPSxi + * period). PPS output enable changes take effect at the next + * PPS leading edge. */ + TIMER3_EMR &= ~(0x3 << TIMER_EMR_EMC0_SHIFT); + if (pps_out_enable) + TIMER3_EMR |= (TIMER_EMR_EMC_SET << TIMER_EMR_EMC0_SHIFT); + /* if PPS output not enabled, keep it clear */ + else + TIMER3_EMR |= (TIMER_EMR_EMC_CLEAR << TIMER_EMR_EMC0_SHIFT); + + /* reset counter at next R0 match: start counting for the + * next second period */ + TIMER3_MCR |= TIMER_MCR_MR0R; + } + + /* clear pending interrupt */ + TIMER3_IR |= TIMER_IR_MR0INT; + } + +} + +/**** API functions ****/ + +void si5351c_set_freq( + si5351c_driver_t* const drv, + uint8_t msn, + double vco, + double freq, + uint8_t rdiv) +{ + double frac, a, b, c; + uint32_t p1, p2, p3; + + // fractional and integer part of pll divisor + if (freq < vco) + frac = modf(vco / freq, &a); + else + frac = modf(freq / vco, &a); + + // fractional expressed as c parts + c = 1024 * 1024 - 1; + b = round(frac * c); + + // convert a,b,c parms to p1,p2,p3 si5351c parms. + p1 = 128 * a + floor(128. * b / c) - 512; + p2 = 128 * b - c * floor(128. * b / c); + p3 = c; + + // set multisynth frequency in given section + si5351c_configure_multisynth(drv, msn, p1, p2, p3, rdiv); +} + +/**** USB API ****/ + +usb_request_status_t usb_vendor_request_time_set_divisor_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static uint32_t divisor = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &divisor, + 4, + NULL, + NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + new_divisor = divisor; + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_set_divisor_one_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static uint32_t divisor = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &divisor, + 4, + NULL, + NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + one_pps_divisor = divisor; + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_set_trig_delay_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static uint32_t trig_delay = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &trig_delay, + 4, + NULL, + NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + new_trig_delay = trig_delay; + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_get_seconds_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + int64_t secs; + if (stage == USB_TRANSFER_STAGE_SETUP) { + secs = seconds; + + usb_transfer_schedule_block(endpoint->in, (uint8_t*) &secs, 8, NULL, NULL); + usb_transfer_schedule_ack(endpoint->out); + } + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_time_set_seconds_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static int64_t secs = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &secs, + 8, + NULL, + NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + seconds = secs; + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_set_seconds_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static int64_t secs = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &secs, + 8, + NULL, + NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + new_seconds = secs; + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_get_ticks_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + uint32_t ticks = timer_get_counter(TIMER3); + + usb_transfer_schedule_block( + endpoint->in, + (uint8_t*) &ticks, + 4, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + } + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_time_set_ticks_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static uint32_t tks = 0; + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block(endpoint->out, (uint8_t*) &tks, 4, NULL, NULL); + return USB_REQUEST_STATUS_OK; + } else if (stage == USB_TRANSFER_STAGE_DATA) { + timer_disable_counter(TIMER3); + timer_set_counter(TIMER3, tks); + timer_enable_counter(TIMER3); + + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} + +usb_request_status_t usb_vendor_request_time_set_clk_freq( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static double freq; + static bool alternate = false; + + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + (uint8_t*) &freq, + 8, + NULL, + NULL); + } else { + if (stage == USB_TRANSFER_STAGE_DATA) { + if (detected_platform() == BOARD_ID_HACKRF1_R9) { + if (alternate) { + si5351c_set_freq( + &clock_gen, + 1, + 800e6, + freq * 2, + 0); + si5351c_set_freq(&clock_gen, 2, 800e6, freq, 0); + } else { + si5351c_set_freq(&clock_gen, 2, 800e6, freq, 0); + si5351c_set_freq( + &clock_gen, + 1, + 800e6, + freq * 2, + 0); + } + alternate = !alternate; + } else { + si5351c_set_freq(&clock_gen, 0, 800e6, freq * 2, 1); + } + usb_transfer_schedule_ack(endpoint->in); + } + } + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_time_set_mcu_clk_sync( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + // change cgu clkin to 10MHz from clock generator (GP_CLKIN) + // WARNING: clock gen out #2 for r9 boards or out #3 for not + // r9 must be already set to give 10MHz. + if (endpoint->setup.value) { + // check for external clock source + activate_best_clock_source(); + + // enable mcu clock: output clock to mcu + si5351c_mcu_clk_enable(true); + + // configure si5351c synthesizer: force int more, remove + // self channel source whenever possible. + si5351c_mcu_clk_sync(&clock_gen, true); + + // set mcu clk pll1 to nominal maximum speed 200 MHz + cpu_clock_pll1_max_speed(CGU_SRC_GP_CLKIN, 20); + + } + // return cgu clkin to 12MHz XTAL + else { + // set mcu clk pll1 to nominal maximum speed 204 MHz + cpu_clock_pll1_max_speed(CGU_SRC_XTAL, 17); + + // restore the configuration of the si5351c synthesizer + si5351c_mcu_clk_sync(&clock_gen, false); + + // disable mcu clock: no output clock to mcu + si5351c_mcu_clk_enable(false); + } + + usb_transfer_schedule_ack(endpoint->in); + } + return USB_REQUEST_STATUS_OK; +} + + +usb_request_status_t usb_vendor_request_time_set_trig_hold_enable_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) + { + if (stage == USB_TRANSFER_STAGE_SETUP) { + new_trig_hold_enable = endpoint->setup.value > 0; + + usb_transfer_schedule_ack(endpoint->in); + } + return USB_REQUEST_STATUS_OK; +} + + +usb_request_status_t usb_vendor_request_time_set_pps_out_enable_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) + { + if (stage == USB_TRANSFER_STAGE_SETUP) { + pps_out_enable = endpoint->setup.value > 0; + + usb_transfer_schedule_ack(endpoint->in); + } + return USB_REQUEST_STATUS_OK; +} + + +usb_request_status_t usb_vendor_request_time_get_pps_in_active( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + uint8_t active = pps_in_active; + + usb_transfer_schedule_block(endpoint->in, (uint8_t*) &active, 1, NULL, NULL); + usb_transfer_schedule_ack(endpoint->out); + } + return USB_REQUEST_STATUS_OK; +} + +/**** end ****/ diff --git a/firmware/hackrf_usb/usb_api_time.h b/firmware/hackrf_usb/usb_api_time.h new file mode 100644 index 000000000..31bb9f610 --- /dev/null +++ b/firmware/hackrf_usb/usb_api_time.h @@ -0,0 +1,91 @@ +/* + * Copyright 2025 Fabrizio Pollastri + * + * 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_TIME_H__ +#define __USB_API_TIME_H__ + +#include +#include +#include + +usb_request_status_t usb_vendor_request_time_set_divisor_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_divisor_one_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_trig_delay_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_get_seconds_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_seconds_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_seconds_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_get_ticks_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_ticks_now( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_clk_freq( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_mcu_clk_sync( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_trig_hold_enable_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_set_pps_out_enable_next_pps( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_time_get_pps_in_active( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +void time_timer_init(void); +void time_set_divisor_next_pps(unsigned long int divisor); +void time_set_divisor_one_pps(unsigned long int divisor); +void time_set_trig_delay_next_pps(unsigned long int trig_delay); +long long int time_get_seconds_now(void); +void time_set_seconds_now(long long int seconds); +void time_set_seconds_next_pps(long long int seconds); +unsigned long int time_get_ticks_now(); +void time_set_ticks_now(uint32_t ticks); + +#endif // __USB_API_TIME_H__ diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index ed388859e..a2d04a112 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -37,7 +37,7 @@ #define USB_PRODUCT_ID (0xFFFF) #endif -#define USB_API_VERSION (0x0108) +#define USB_API_VERSION (0x010A) #define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF) diff --git a/host/hackrf-tools/src/CMakeLists.txt b/host/hackrf-tools/src/CMakeLists.txt index 53a54edd7..643db7b0a 100644 --- a/host/hackrf-tools/src/CMakeLists.txt +++ b/host/hackrf-tools/src/CMakeLists.txt @@ -30,7 +30,8 @@ set(TOOLS hackrf_debug hackrf_clock hackrf_operacake - hackrf_biast) + hackrf_biast + hackrf_time) if(TARGET HackRF::hackrf) list(APPEND TOOLS_LINK_LIBS HackRF::hackrf) diff --git a/host/hackrf-tools/src/hackrf_clock.c b/host/hackrf-tools/src/hackrf_clock.c index 9d293af4c..1aa6381e1 100644 --- a/host/hackrf-tools/src/hackrf_clock.c +++ b/host/hackrf-tools/src/hackrf_clock.c @@ -195,7 +195,8 @@ int si5351c_read_multisynth_config(hackrf_device* device, const uint_fast8_t ms_ if (p3) { printf("\tOutput (800Mhz PLL): %#.10f Mhz\n", ((double) 800 / - (double) (((double) p1 * p3 + p2 + 512 * p3) / (double) (128 * p3))) / + (double) (((double) p1 * p3 + p2 + 512 * p3) / + (double) (128 * p3))) / div_lut[r_div]); } } else { diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 73f980905..b83588681 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -259,7 +259,8 @@ int si5351c_read_multisynth_config(hackrf_device* device, const uint_fast8_t ms_ if (p3) { printf("\tOutput (800Mhz PLL): %#.10f Mhz\n", ((double) 800 / - (double) (((double) p1 * p3 + p2 + 512 * p3) / (double) (128 * p3))) / + (double) (((double) p1 * p3 + p2 + 512 * p3) / + (double) (128 * p3))) / div_lut[r_div]); } } else { diff --git a/host/hackrf-tools/src/hackrf_sweep.c b/host/hackrf-tools/src/hackrf_sweep.c index 4e0e5ec17..598f31d15 100644 --- a/host/hackrf-tools/src/hackrf_sweep.c +++ b/host/hackrf-tools/src/hackrf_sweep.c @@ -371,7 +371,8 @@ int rx_callback(hackrf_transfer* transfer) time_str, (long int) usb_transfer_time.tv_usec, (uint64_t) (frequency + (DEFAULT_SAMPLE_RATE_HZ / 2)), - (uint64_t) (frequency + ((DEFAULT_SAMPLE_RATE_HZ * 3) / 4)), + (uint64_t) (frequency + + ((DEFAULT_SAMPLE_RATE_HZ * 3) / 4)), fft_bin_width, num_fft_bins); for (i = 0; (num_fft_bins / 4) > i; i++) { diff --git a/host/hackrf-tools/src/hackrf_time.c b/host/hackrf-tools/src/hackrf_time.c new file mode 100644 index 000000000..af0925d33 --- /dev/null +++ b/host/hackrf-tools/src/hackrf_time.c @@ -0,0 +1,380 @@ +/* + * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012 Jared Boone + * Copyright 2013 Benjamin Vernoux + * Copyright 2017 Dominic Spill + * Copyright 2025 Fabrizio Pollastri + * + * 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 +#include +#include + +//#ifndef bool +//typedef int bool; +// #define true 1 +// #define false 0 +//#endif + +int parse_int(char* s, uint32_t* const value) +{ + uint_fast8_t base = 10; + char* s_end; + long long_value; + + if (strlen(s) > 2) { + if (s[0] == '0') { + if ((s[1] == 'x') || (s[1] == 'X')) { + base = 16; + s += 2; + } else if ((s[1] == 'b') || (s[1] == 'B')) { + base = 2; + s += 2; + } + } + } + + s_end = s; + long_value = strtol(s, &s_end, base); + if ((s != s_end) && (*s_end == 0)) { + *value = (uint32_t) long_value; + return HACKRF_SUCCESS; + } else { + return HACKRF_ERROR_INVALID_PARAM; + } +} + +int parse_double(char* s, double* const double_value) +{ + char* s_end; + + s_end = s + strlen(s); + *double_value = strtof(s, &s_end); + if ((s != s_end) && (*s_end == 0)) { + return HACKRF_SUCCESS; + } else { + return HACKRF_ERROR_INVALID_PARAM; + } +} + +static void usage() +{ + printf("Manage HTime for HackRF: ticks, seconds, A/D trigger, clocks.\n"); + printf("\nUsage:\n"); + printf("\t-h, --help: this help\n"); + printf("\t-a, --pps_in_active: read PPS input active status (0/1)\n"); + printf("\t-d, --divisor : set divisor to val next pps\n"); + printf("\t-D, --Divisor : set divisor to val for one pps\n"); + printf("\t-f, --set_clk_freq : set sync clock freq to \n"); + printf("\t-k, --ticks : read ticks counter now\n"); + printf("\t-K, --Ticks : set ticks counter now\n"); + printf("\t-l, --trig_hold <0/1>: set trig hold state (1: sample next PPS, active one PPS later; 0: active next PPS)\n"); + printf("\t-p, --pps_out <0/1>: set pps output state at next PPS leading edge\n"); + printf("\t-r, --trig_delay : set trig delay to val next pps\n"); + printf("\t-s, --seconds : set seconds counter now to value\n"); + printf("\t-S, --Seconds : as -s but in sync to next pps\n"); + printf("\t-t, --get_seconds: read seconds counter now\n"); + printf("\t-y, --mcu_clk_sync <0/1>: enable/disable mcu clock sync\n"); + printf("\nExamples:\n"); + printf("\thackrf_time -s 1234 # set seconds counter to 1234 now\n"); + printf("\thackrf_time -l 1 # enable trigger hold on next PPS\n"); + printf("\thackrf_time -p 0 # disable PPS output on next PPS\n"); + printf("\thackrf_time -y 1 # enable MCU sync mode\n"); + + printf("\nv0.1.0 20250215 F.P. \n"); +} + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"pps_in_active", no_argument, 0, 'a'}, + {"divisor", required_argument, 0, 'd'}, + {"Divisor", required_argument, 0, 'D'}, + {"set_clk_freq", required_argument, 0, 'f'}, + {"ticks", no_argument, 0, 'k'}, + {"Ticks", required_argument, 0, 'K'}, + {"trig_hold", required_argument, 0, 'l'}, + {"pps_out", required_argument, 0, 'p'}, + {"trig_delay", required_argument, 0, 'r'}, + {"seconds", required_argument, 0, 's'}, + {"Seconds", required_argument, 0, 'S'}, + {"get_seconds", no_argument, 0, 't'}, + {"mcu_clk_sync", required_argument, 0, 'y'}, + {0, 0, 0, 0}}; + +int main(int argc, char** argv) +{ + int opt; + hackrf_device* device = NULL; + int option_index = 0; + const char* serial_number = NULL; + + uint32_t divisor; + uint32_t trig_delay; + int64_t seconds; + uint32_t ticks; + uint32_t value; + uint8_t pps_in_active; + double frequency; + + if (argc > 1) + opt = getopt_long( + argc, + argv, + "had:D:f:kK:il:p:r:s:S:ty:", + long_options, + &option_index); + else { + usage(); + return 0; + } + + int result = hackrf_init(); + if (result) { + printf("hackrf_init() failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + + result = hackrf_open_by_serial(serial_number, &device); + if (result) { + printf("hackrf_open() failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + + switch (opt) { + case 'a': + result = hackrf_time_get_pps_in_active(device, &pps_in_active); + if (result) { + printf("pps input activity reading failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + printf("pps input active: %u\n", pps_in_active ? 1 : 0); + break; + + case 'd': + result = parse_int(optarg, &value); + if (result) { + printf("invalid divisor value. Int required'\n"); + return EXIT_FAILURE; + } + divisor = value; + result = hackrf_time_set_divisor_next_pps(device, divisor); + if (result) { + printf("divisor next pps setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 'D': + result = parse_int(optarg, &value); + if (result) { + printf("invalid divisor value. Int required'\n"); + return EXIT_FAILURE; + } + divisor = value; + result = hackrf_time_set_divisor_one_pps(device, divisor); + if (result) { + printf("divisor one pps setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 'f': + result = parse_double(optarg, &frequency); + if (result) { + printf("invalid frequency value. Double required'\n"); + return EXIT_FAILURE; + } + result = hackrf_time_set_clk_freq(device, frequency); + if (result) { + printf("clock frequency setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 'k': + result = hackrf_time_get_ticks_now(device, &ticks); + if (result) { + printf("ticks reading failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + printf("tick counter: %d\n", ticks); + break; + + case 'K': + result = hackrf_time_set_ticks_now(device, ticks); + if (result) { + printf("tick setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + + case 'l': + result = parse_int(optarg, &value); + if (result) { + printf("invalid enable value. Int required'\n"); + return EXIT_FAILURE; + } + result = hackrf_time_set_trig_hold_enable_next_pps(device, value); + if (result) { + printf("setting trigger hold failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + + case 'p': + result = parse_int(optarg, &value); + if (result) { + printf("invalid enable value. Int required'\n"); + return EXIT_FAILURE; + } + result = hackrf_time_set_pps_out_enable_next_pps(device, value); + if (result) { + printf("setting pps output failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + + case 'r': + result = parse_int(optarg, &value); + if (result) { + printf("invalid delay value. Int required'\n"); + return EXIT_FAILURE; + } + trig_delay = value; + result = hackrf_time_set_trig_delay_next_pps(device, trig_delay); + if (result) { + printf("trigger delay setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 's': + result = parse_int(optarg, &value); + if (result) { + printf("invalid second value. Int required'\n"); + return EXIT_FAILURE; + } + seconds = value; + result = hackrf_time_set_seconds_now(device, seconds); + if (result) { + printf("second setting failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 'S': + result = parse_int(optarg, &value); + if (result) { + printf("invalid second value. Int required'\n"); + return EXIT_FAILURE; + } + seconds = value; + result = hackrf_time_set_seconds_next_pps(device, seconds); + if (result) { + printf("second setting at next pps failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 't': + result = hackrf_time_get_seconds_now(device, &seconds); + if (result) { + printf("seconds reading failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + printf("second counter: %ld\n", seconds); + break; + + case 'y': + result = parse_int(optarg, &value); + if (result) { + printf("invalid enable value. Int required'\n"); + return EXIT_FAILURE; + } + result = hackrf_time_set_mcu_clk_sync(device, value); + if (result) { + printf("seconds setting at next pps failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + break; + + case 'h': + case '?': + usage(); + return EXIT_SUCCESS; + default: + fprintf(stderr, "unknown argument '-%c %s'\n", opt, optarg); + usage(); + return EXIT_FAILURE; + } + + if (result != HACKRF_SUCCESS) { + printf("argument error: %s (%d)\n", hackrf_error_name(result), result); + usage(); + return EXIT_FAILURE; + } + + result = hackrf_close(device); + if (result) { + printf("hackrf_close() failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + + hackrf_exit(); + return EXIT_SUCCESS; +} diff --git a/host/hackrf-tools/src/hackrf_transfer.c b/host/hackrf-tools/src/hackrf_transfer.c index ef5d68e1d..19c826f5b 100644 --- a/host/hackrf-tools/src/hackrf_transfer.c +++ b/host/hackrf-tools/src/hackrf_transfer.c @@ -1117,7 +1117,9 @@ int main(int argc, char** argv) // Change the freq and sample rate to correct the crystal clock error. if (crystal_correct) { sample_rate_hz = - (uint32_t) ((double) sample_rate_hz * (1000000 - crystal_correct_ppm) / 1000000 + 0.5); + (uint32_t) ((double) sample_rate_hz * + (1000000 - crystal_correct_ppm) / 1000000 + + 0.5); freq_hz = freq_hz * (1000000 - crystal_correct_ppm) / 1000000; } diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index ae19ef19e..a6b803e2d 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -2,6 +2,7 @@ Copyright (c) 2012-2022 Great Scott Gadgets Copyright (c) 2012, Jared Boone Copyright (c) 2013, Benjamin Vernoux +Copyright (c) 2025, Fabrizio Pollastri All rights reserved. @@ -44,11 +45,13 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI #define TO_LE64(x) __builtin_bswap64(x) #define FROM_LE16(x) __builtin_bswap16(x) #define FROM_LE32(x) __builtin_bswap32(x) + #define FROM_LE64(x) __builtin_bswap64(x) #else #define TO_LE(x) x #define TO_LE64(x) x #define FROM_LE16(x) x #define FROM_LE32(x) x + #define FROM_LE64(x) x #endif // TODO: Factor this into a shared #include so that firmware can use @@ -100,6 +103,22 @@ typedef enum { HACKRF_VENDOR_REQUEST_SUPPORTED_PLATFORM_READ = 46, HACKRF_VENDOR_REQUEST_SET_LEDS = 47, HACKRF_VENDOR_REQUEST_SET_USER_BIAS_T_OPTS = 48, + + /* HTime extension */ + HACKRF_VENDOR_REQUEST_TIME_SET_DIVISOR_NEXT_PPS, + HACKRF_VENDOR_REQUEST_TIME_SET_DIVISOR_ONE_PPS, + HACKRF_VENDOR_REQUEST_TIME_SET_TRIG_DELAY_NEXT_PPS, + HACKRF_VENDOR_REQUEST_TIME_GET_SECONDS_NOW, + HACKRF_VENDOR_REQUEST_TIME_SET_SECONDS_NOW, + HACKRF_VENDOR_REQUEST_TIME_SET_SECONDS_NEXT_PPS, + HACKRF_VENDOR_REQUEST_TIME_GET_TICKS_NOW, + HACKRF_VENDOR_REQUEST_TIME_SET_TICKS_NOW, + HACKRF_VENDOR_REQUEST_TIME_SET_CLK_FREQ, + HACKRF_VENDOR_REQUEST_TIME_SET_MCU_CLK_SYNC, + HACKRF_VENDOR_REQUEST_TIME_SET_TRIG_HOLD_ENABLE, + HACKRF_VENDOR_REQUEST_TIME_SET_PPS_OUT_ENABLE, + HACKRF_VENDOR_REQUEST_TIME_GET_PPS_IN_ACTIVE, + } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -3022,6 +3041,343 @@ int ADDCALL hackrf_set_user_bias_t_opts( } } +/* HTime API FP20230805 */ + +int ADDCALL hackrf_time_set_divisor_next_pps(hackrf_device* device, const uint32_t divisor) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + uint32_t divisor_le; + divisor_le = TO_LE(divisor); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_DIVISOR_NEXT_PPS, + 0, + 0, + (uint8_t*) &divisor_le, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_divisor_one_pps(hackrf_device* device, const uint32_t divisor) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + uint32_t divisor_le; + divisor_le = TO_LE(divisor); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_DIVISOR_ONE_PPS, + 0, + 0, + (uint8_t*) &divisor_le, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_trig_delay_next_pps( + hackrf_device* device, + const uint32_t trig_delay) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + uint32_t trig_delay_le; + trig_delay_le = TO_LE(trig_delay); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_TRIG_DELAY_NEXT_PPS, + 0, + 0, + (uint8_t*) &trig_delay_le, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_get_seconds_now(hackrf_device* device, int64_t* seconds) +{ + USB_API_REQUIRED(device, 0x0109) + + int64_t secs; + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_GET_SECONDS_NOW, + 0, + 0, + (uint8_t*) &secs, + 8, + 0); + + if (result < 8) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + *seconds = FROM_LE64(secs); + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_seconds_now(hackrf_device* device, const int64_t seconds) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + int64_t seconds_le; + seconds_le = TO_LE64(seconds); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_SECONDS_NOW, + 0, + 0, + (uint8_t*) &seconds_le, + 8, + 0); + + if (result < 8) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_seconds_next_pps(hackrf_device* device, const int64_t seconds) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + int64_t seconds_le; + seconds_le = TO_LE64(seconds); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_SECONDS_NEXT_PPS, + 0, + 0, + (uint8_t*) &seconds_le, + 8, + 0); + + if (result < 8) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_get_ticks_now(hackrf_device* device, uint32_t* ticks) +{ + USB_API_REQUIRED(device, 0x0109) + + uint32_t tks; + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_GET_TICKS_NOW, + 0, + 0, + (uint8_t*) &tks, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + *ticks = FROM_LE32(tks); + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_ticks_now(hackrf_device* device, const uint32_t ticks) +{ + USB_API_REQUIRED(device, 0x0109) + + int result; + uint32_t ticks_le; + ticks_le = TO_LE(ticks); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_TICKS_NOW, + 0, + 0, + (uint8_t*) &ticks_le, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_clk_freq(hackrf_device* device, const double clk_freq) +{ + int result; + double clk_freq_le; + clk_freq_le = TO_LE64(clk_freq); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_CLK_FREQ, + 0, + 0, + (uint8_t*) &clk_freq_le, + 8, + 0); + + if (result < 8) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_mcu_clk_sync(hackrf_device* device, const uint8_t value) +{ + USB_API_REQUIRED(device, 0x0109) + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_MCU_CLK_SYNC, + value, + 0, + NULL, + 0, + 0); + + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_trig_hold_enable_next_pps(hackrf_device* device, const uint8_t value) +{ + USB_API_REQUIRED(device, 0x010A) + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_TRIG_HOLD_ENABLE, + value, + 0, + NULL, + 0, + 0); + + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_set_pps_out_enable_next_pps(hackrf_device* device, const uint8_t value) +{ + USB_API_REQUIRED(device, 0x010A) + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_SET_PPS_OUT_ENABLE, + value, + 0, + NULL, + 0, + 0); + + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_time_get_pps_in_active(hackrf_device* device, uint8_t* active) +{ + USB_API_REQUIRED(device, 0x010A) + + int result; + uint8_t value = 0; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_TIME_GET_PPS_IN_ACTIVE, + 0, + 0, + &value, + 1, + 0); + + if (result < 1) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + *active = value; + return HACKRF_SUCCESS; + } +} + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index faa3d4027..7b196832d 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -2,6 +2,8 @@ Copyright (c) 2012-2022 Great Scott Gadgets Copyright (c) 2012, Jared Boone Copyright (c) 2013, Benjamin Vernoux +Copyright (c) 2025, Fabrizio Pollastri + All rights reserved. @@ -2100,6 +2102,46 @@ extern ADDAPI int ADDCALL hackrf_set_user_bias_t_opts( hackrf_device* device, hackrf_bias_t_user_settting_req* req); +/* HTime API FP20230805 */ + +extern ADDAPI int ADDCALL hackrf_time_set_divisor_next_pps( + hackrf_device* device, + uint32_t divisor); +extern ADDAPI int ADDCALL hackrf_time_set_divisor_one_pps( + hackrf_device* device, + uint32_t divisor); +extern ADDAPI int ADDCALL hackrf_time_set_trig_delay_next_pps( + hackrf_device* device, + uint32_t trig_delay); +extern ADDAPI int ADDCALL hackrf_time_get_seconds_now( + hackrf_device* device, + int64_t* seconds); +extern ADDAPI int ADDCALL hackrf_time_set_seconds_now( + hackrf_device* device, + int64_t seconds); +extern ADDAPI int ADDCALL hackrf_time_set_seconds_next_pps( + hackrf_device* device, + int64_t seconds); +extern ADDAPI int ADDCALL hackrf_time_get_ticks_now( + hackrf_device* device, + uint32_t* ticks); +extern ADDAPI int ADDCALL hackrf_time_set_ticks_now(hackrf_device* device, uint32_t ticks); +extern ADDAPI int ADDCALL hackrf_time_set_clk_freq( + hackrf_device* device, + const double clk_freq); +extern ADDAPI int ADDCALL hackrf_time_set_mcu_clk_sync( + hackrf_device* device, + const uint8_t value); +extern ADDAPI int ADDCALL hackrf_time_set_trig_hold_enable_next_pps( + hackrf_device* device, + const uint8_t value); +extern ADDAPI int ADDCALL hackrf_time_set_pps_out_enable_next_pps( + hackrf_device* device, + const uint8_t value); +extern ADDAPI int ADDCALL hackrf_time_get_pps_in_active( + hackrf_device* device, + uint8_t* active); + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/tools/reformat-source.sh b/tools/reformat-source.sh index 754717c4e..f94213f3a 100755 --- a/tools/reformat-source.sh +++ b/tools/reformat-source.sh @@ -1,6 +1,7 @@ #!/bin/bash -VERSION=`clang-format --version | grep -o '[^ ]*$' | cut -d '.' -f 1` +VERSION=`clang-format --version | grep -Po '(?<=\s)\d+(?=\.\d+\.\d+\s)'` + if [ "$VERSION" -ge "14" ]; then CLANG_FORMAT=clang-format elif clang-format-14 --version > /dev/null; then