Skip to content

Commit

Permalink
blocks/sources/hackrf: add HackRFSource
Browse files Browse the repository at this point in the history
resolves #18.
  • Loading branch information
vsergeev committed Jul 17, 2016
1 parent b07468d commit c54ff0d
Show file tree
Hide file tree
Showing 2 changed files with 279 additions and 0 deletions.
1 change: 1 addition & 0 deletions radio/blocks/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ return {
SignalSource = require('radio.blocks.sources.signal'),
RtlSdrSource = require('radio.blocks.sources.rtlsdr'),
AirspySource = require('radio.blocks.sources.airspy'),
HackRFSource = require('radio.blocks.sources.hackrf'),

-- Sink Blocks
IQFileSink = require('radio.blocks.sinks.iqfile'),
Expand Down
278 changes: 278 additions & 0 deletions radio/blocks/sources/hackrf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
---
-- Source a complex-valued signal from a HackRF One. This source requires the
-- libhackrf library.
--
-- @category Sources
-- @block HackRFSource
-- @tparam number frequency Tuning frequency in Hz
-- @tparam number rate Sample rate in Hz
-- @tparam[opt={}] table options Additional options, specifying:
-- * `lna_gain` (int, default 8 dB, range 0 to 40 dB, 8 dB step)
-- * `vga_gain` (int, default 20 dB, range 0 to 62 dB, 2 dB step)
-- * `baseband_bandwidth` (number in Hz, default round down from sample rate)
-- * `rf_amplifier_enable` (bool, default false)
-- * `antenna_power_enable` (bool, default false)
--
-- @signature > out:ComplexFloat32
--
-- @usage
-- -- Source samples from 135 MHz sampled at 10 MHz
-- local src = radio.HackRFSource(135e6, 10e6)
--
-- -- Source samples from 91.1 MHz sampled at 4 MHz, with custom gain settings
-- local src = radio.HackRFSource(91.1e6, 4e6, {lna_gain = 16, vga_gain = 22})
--
-- -- Source samples from 144.390 MHz sampled at 4 MHz, with antenna power enabled
-- local src = radio.HackRFSource(144.390e6, 4e6, {antenna_power_enable = true})

local ffi = require('ffi')

local block = require('radio.core.block')
local platform = require('radio.core.platform')
local debug = require('radio.core.debug')
local types = require('radio.types')
local async = require('radio.core.async')

local HackRFSource = block.factory("HackRFSource")

function HackRFSource:instantiate(frequency, rate, options)
self.frequency = assert(frequency, "Missing argument #1 (frequency)")
self.rate = assert(rate, "Missing argument #2 (rate)")

self.options = options or {}
self.lna_gain = self.options.lna_gain or 8
self.vga_gain = self.options.vga_gain or 40
self.baseband_bandwidth = self.options.baseband_bandwidth
self.rf_amplifier_enable = self.options.rf_amplifier_enable or false
self.antenna_power_enable = self.options.antenna_power_enable or false

self:add_type_signature({}, {block.Output("out", types.ComplexFloat32)})
end

function HackRFSource:get_rate()
return self.rate
end

ffi.cdef[[
typedef struct hackrf_device hackrf_device;

typedef struct {
hackrf_device* device;
uint8_t* buffer;
int buffer_length;
int valid_length;
void* rx_ctx;
void* tx_ctx;
} hackrf_transfer;

typedef int (*hackrf_sample_block_cb_fn)(hackrf_transfer* transfer);

const char* hackrf_error_name(int errcode);

int hackrf_init(void);
int hackrf_exit(void);

int hackrf_open(hackrf_device** device);
int hackrf_close(hackrf_device* device);

int hackrf_board_id_read(hackrf_device* device, uint8_t* value);
const char* hackrf_board_id_name(int board_id);
int hackrf_version_string_read(hackrf_device* device, char* version, uint8_t length);

int hackrf_start_rx(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* rx_ctx);
int hackrf_stop_rx(hackrf_device* device);

int hackrf_set_freq(hackrf_device* device, const uint64_t freq_hz);
int hackrf_set_sample_rate(hackrf_device* device, const double freq_hz);
int hackrf_set_sample_rate_manual(hackrf_device* device, const uint32_t freq_hz, const uint32_t divider);
int hackrf_set_baseband_filter_bandwidth(hackrf_device* device, const uint32_t bandwidth_hz);
int hackrf_set_lna_gain(hackrf_device* device, uint32_t value);
int hackrf_set_vga_gain(hackrf_device* device, uint32_t value);
int hackrf_set_amp_enable(hackrf_device* device, const uint8_t value);
int hackrf_set_antenna_enable(hackrf_device* device, const uint8_t value);

uint32_t hackrf_compute_baseband_filter_bw_round_down_lt(const uint32_t bandwidth_hz);
uint32_t hackrf_compute_baseband_filter_bw(const uint32_t bandwidth_hz);
]]
local libhackrf_available, libhackrf = pcall(ffi.load, "hackrf")

function HackRFSource:initialize()
-- Check library is available
if not libhackrf_available then
error("HackRFSource: libhackrf not found. Is libhackrf installed?")
end
end

function HackRFSource:initialize_hackrf()
self.dev = ffi.new("struct hackrf_device *[1]")

local ret

-- Initialize library
ret = libhackrf.hackrf_init()
if ret ~= 0 then
error("hackrf_init(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Open device
ret = libhackrf.hackrf_open(self.dev)
if ret ~= 0 then
error("hackrf_open(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Dump version info
if debug.enabled then
-- Look up firmware version
local firmware_version = ffi.new("char[128]")
ret = libhackrf.hackrf_version_string_read(self.dev[0], firmware_version, 128)
if ret ~= 0 then
error("hackrf_version_string_read(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end
firmware_version = ffi.string(firmware_version)

-- Look up board ID
local board_id = ffi.new("uint8_t[1]")
ret = libhackrf.hackrf_board_id_read(self.dev[0], board_id)
if ret ~= 0 then
error("hackrf_board_id_read(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end
board_id = ffi.string(libhackrf.hackrf_board_id_name(board_id[0]))

debug.printf("[HackRFSource] Firmware version: %s\n", firmware_version)
debug.printf("[HackRFSource] Board ID: %s\n", board_id)
end

-- Set sample rate
ret = libhackrf.hackrf_set_sample_rate(self.dev[0], self.rate)
if ret ~= 0 then
error("hackrf_set_sample_rate(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Compute baseband filter bandwidth
local computed_baseband_bandwidth
if self.baseband_bandwidth then
-- Snap supplied bandwidth to closest
computed_baseband_bandwidth = ffi.C.hackrf_compute_baseband_filter_bw(self.baseband_bandwidth)
else
-- Round down from sample rate
computed_baseband_bandwidth = ffi.Chackrf_compute_baseband_filter_bw_round_down_lt(self.rate)
end

debug.printf("[HackRFSource] Frequency: %u Hz, Sample rate: %u Hz\n", self.frequency, self.rate)
debug.printf("[HackRFSource] Requested Bandwidth: %u Hz, Actual Bandwidth: %u Hz\n", self.baseband_bandwidth or computed_baseband_bandwidth, computed_baseband_bandwidth)

-- Set baseband filter bandwidth
ret = libhackrf.hackrf_set_baseband_filter_bandwidth(self.dev[0], computed_baseband_bandwidth)
if ret ~= 0 then
error("hackrf_set_baseband_filter_bandwidth(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Set LNA gain
ret = libhackrf.hackrf_set_lna_gain(self.dev[0], self.lna_gain)
if ret ~= 0 then
error("hackrf_set_lna_gain(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Set VGA gain
ret = libhackrf.hackrf_set_vga_gain(self.dev[0], self.vga_gain)
if ret ~= 0 then
error("hackrf_set_vga_gain(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Set RF amplifier enable
ret = libhackrf.hackrf_set_amp_enable(self.dev[0], self.rf_amplifier_enable)
if ret ~= 0 then
error("hackrf_set_amp_enable(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Set antenna power enable
ret = libhackrf.hackrf_set_antenna_enable(self.dev[0], self.antenna_power_enable)
if ret ~= 0 then
error("hackrf_set_antenna_enable(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Set frequency
ret = libhackrf.hackrf_set_freq(self.dev[0], self.frequency)
if ret ~= 0 then
error("hackrf_set_freq(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end
end

local function read_callback_factory(...)
local ffi = require('ffi')
local radio = require('radio')

local fds = {...}

local out = radio.ComplexFloat32.vector()

local function read_callback(transfer)
-- Resize output vector
out:resize(transfer.valid_length/2)

-- Convert complex u8 in buf to complex floats in output vector
for i = 0, out.length-1 do
out.data[i].real = (transfer.buffer[2*i] - 127.5) * (1/127.5)
out.data[i].imag = (transfer.buffer[2*i+1] - 127.5) * (1/127.5)
end

-- Write to each output fd
for i = 1, #fds do
local total_bytes_written = 0
while total_bytes_written < out.size do
local bytes_written = tonumber(ffi.C.write(fds[i], ffi.cast("uint8_t *", out.data) + total_bytes_written, out.size - total_bytes_written))
if bytes_written <= 0 then
error("write(): " .. ffi.string(ffi.C.strerror(ffi.errno())))
end

total_bytes_written = total_bytes_written + bytes_written
end
end

return 0
end

return ffi.cast('int (*)(hackrf_transfer *)', read_callback)
end

function HackRFSource:run()
-- Initialize the hackrf in our own running process
self:initialize_hackrf()

-- Build signal set with SIGTERM
local sigset = ffi.new("sigset_t[1]")
ffi.C.sigemptyset(sigset)
ffi.C.sigaddset(sigset, ffi.C.SIGTERM)

-- Block handling of SIGTERM
if ffi.C.sigprocmask(ffi.C.SIG_BLOCK, sigset, nil) ~= 0 then
error("sigprocmask(): " .. ffi.string(ffi.C.strerror(ffi.errno())))
end

-- Start receiving
local read_callback, read_callback_state = async.callback(read_callback_factory, unpack(self.outputs[1]:filenos()))
local ret = libhackrf.hackrf_start_rx(self.dev[0], read_callback, nil)
if ret ~= 0 then
error("hackrf_start_rx(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Wait for SIGTERM
local sig = ffi.new("int[1]")
if ffi.C.sigwait(sigset, sig) ~= 0 then
error("sigwait(): " .. ffi.string(ffi.C.strerror(ffi.errno())))
end

-- Stop receiving
ret = libhackrf.hackrf_stop_rx(self.dev[0])
if ret ~= 0 then
error("hackrf_stop_rx(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end

-- Close hackrf
ret = libhackrf.hackrf_close(self.dev[0])
if ret ~= 0 then
error("hackrf_close(): " .. ffi.string(libhackrf.hackrf_error_name(ret)))
end
end

return HackRFSource

0 comments on commit c54ff0d

Please sign in to comment.