-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question]: What is the granularity of sample rate? #1223
Comments
Something in between. The HackRF's Si5351 clock generator produces an 800MHz clock internally, which is fed to multiple programmable synthesizers, each of which has a fractional divider. If one of those units can produce twice your desired sample rate, then it can be used exactly. The relevant calculation can be seen here: hackrf/firmware/common/hackrf_core.c Lines 352 to 389 in 15636ef
The If your values hit the |
I wrote a quick script to check which values can be set exactly, and it looks like both 4,800,000 Hz and 14,318,180 Hz can be set
Edit: see corrected script below. |
I have a question about the code you linked to here. I know that on a PC (an Intel x86 processor) floating point rounding can be configured to use one of 4 different methods. However, this code that you linked to is not PC-side code. It's firmware for the HackRF itself. What method of rounding does the processor in the HackRF use? Can it be configured to use different methods? And what method is used in the official firmware, who's code you linked to here? Also, the code above that tests if it can use an exact frequency, or only an approximate frequency, doesn't seem to output what that approximate frequency will be. If I select a sample rate that can only be used approximately, the above algorithm outputs 3 values stored in variables called a, b, and c. But how are those 3 values then converted into the frequency that the HackRF will actually use? |
Well, how they're actually converted into the frequency is that the values |
The previous script was not quite right, although it was correct about the exact matches for 4.8MHz and 14.318180 MHz. Below is a corrected script, which displays all the results including the effective divisor, resulting sample rate and offset from target. The two rates you were interested in:
Example of an approximate match:
Example of setting an exact samplerate of fractional Hz:
The script: from math import gcd
import sys
if len(sys.argv) < 2 or len(sys.argv) > 3:
print("Usage: %s <numerator> [denominator]" % sys.argv[0])
print("where target rate in Hz is (numerator / denominator)")
print("The denominator defaults to 1")
sys.exit(1)
# Get target sample rate from command line.
target_num = int(sys.argv[1])
if len(sys.argv) == 3:
target_denom = int(sys.argv[2])
else:
target_denom = 1
target_rate = target_num / target_denom
print("Target sample rate: %d / %d = %f Hz" %
(target_num, target_denom, target_rate))
# Calculate closest match using same method as firmware.
VCO_FREQ = 800 * 1000 * 1000
rate_num = target_num * 2
rate_denom = target_denom
a = (VCO_FREQ * rate_denom) // rate_num
rem = (VCO_FREQ * rate_denom) - (a * rate_num)
if rem == 0:
print("Exact match, integer mode")
b = 0
c = 1
else:
g = gcd(rem, rate_num)
rem //= g
rate_num //= g
if rate_num < (1 << 20):
print("Exact match, fractional mode")
b = rem
c = rate_num
else:
print("Approximate match only")
c = (1 << 20) - 1
b = (c * rem) // rate_num
g = gcd(b, c)
b //= g
c //= g
# Calculate resulting divisor and frequencies.
divisor = a + b / c
double_rate = VCO_FREQ / divisor
actual_rate = double_rate / 2
rate_error = actual_rate - target_rate
print("VCO frequency: %d Hz" % VCO_FREQ)
print("Divisor: %d + %d / %d = %f" % (a, b, c, divisor))
print("Double sample rate: %f Hz" % double_rate)
print("Actual sample rate: %f Hz" % actual_rate)
print("Sample rate error: %f Hz" % rate_error) |
Vectorising the error calculation over all integer samplerates from 1 Hz to 20 MHz gives some interesting results:
from matplotlib.pyplot import *
import numpy as np
VCO_FREQ = 800 * 1000 * 1000
target_num = np.arange(1, 20000001)
target_denom = 1
target_rate = target_num / target_denom
rate_num = target_num * 2
rate_denom = 1
a = (VCO_FREQ * rate_denom) // rate_num
b = np.empty_like(a)
c = np.empty_like(a)
rem = (VCO_FREQ * rate_denom) - (a * rate_num)
int_mode = rem == 0
b[int_mode] = 0
c[int_mode] = 1
frac = ~int_mode
g = np.gcd(rem[frac], rate_num[frac])
rem[frac] //= g
rate_num[frac] //= g
exact = frac & (rate_num < (1 << 20))
b[exact] = rem[exact]
c[exact] = rate_num[exact]
approx = frac & ~exact
c[approx] = (1 << 20) - 1
b[approx] = (c[approx] * rem[approx]) // rate_num[approx]
g = np.gcd(b[approx], c[approx])
b[approx] //= g
c[approx] //= g
divisor = a + b / c
double_rate = VCO_FREQ / divisor
actual_rate = double_rate / 2
rate_error = actual_rate - target_rate
plot(rate_error)
title("Sample rate error vs target sample rate")
xlabel("Sample rate (Hz)")
ylabel("Sample rate error (Hz)")
show() |
What would you like to know?
How precisely can I set the HackRF's sample rate? To the nearest MHz? Or to the nearest Hz? Or something in between?
I'm asking because I was wondering if I could set the sample rate to 4,800,000 MHz. This would be ideal for audio, as it's an exact power-of-ten multiple of 48kHz (a common sound card sample rate) which would greatly simplify the algorithm used to upscale audio data from my microphone to the HackRF sample rate, so as to be able to transmit live audio coming into my sound card from the microphone.
Likewise if I could set the sample rate of the HackRF to exactly 14,318,180Hz (14.31880MHz) this would be great, as this is an exact multiple (4 times, specifically) of the chroma carrier frequency in NTSC TV. This sample rate would put the chroma carrier exactly half way between the 0Hz and the nyquist limit. If this exact sample rate (accurate to 1Hz precision) could be used with the HackRF, it would greatly simplify software I'm writing to transmit or receive NTSC video. With both transmitting and receiving, it would allow my image frame's full width (including all sync and blanking) to be an integer number (so it can be measured in pixels, and I don't have to worry about fractions of a pixel in the width of the image). Specifically the image width would be 910 pixels in this case. For receiving specifically, it would also make it much easier to separate the luma and chroma signals, using simply a horizontal kernel filter of (1, 0, 1) for extracting luma and a horizontal kernel filter of (-1, 0, 1) for extracting chroma.
The text was updated successfully, but these errors were encountered: