mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
Пункт 3 плана. Ручные подгонки остаются: ppm работает ПОД ними, а не вместо — уход тракта где-то больше, где-то меньше. - Settings: TCalibration.FreqCalPPM (JSON cal_freq_ppm, +-100 ppm, per-device). Смысл знака: «опора выше номинала на N ppm», положительное значение поднимает показываемые частоты. - RadioBackend: виртуальный SetFreqCalPPM. - HPSDRNetwork: CalFreq(Hz) = Hz / (1 + ppm) в частотном слове — все четыре точки (главный DDC, DDC панов, feedback-DDC PureSignal, DUC передачи). Тактовый генератор радио менять нечем, поэтому правим запрос. - PlutoBackend: штатный xo_correction в ad9361-phy (device-attr биндинги добавлены в IIOBindings). Номинал XO снимается при коннекте с округлением до 100 кГц, чтобы наша же коррекция с прошлой сессии не накапливалась; после записи LO переписываются, иначе правка ждала бы следующей перестройки. Одна цифра лечит RX и TX на всех диапазонах. - QO-100: TXvtrEntry.TxLOError (JSON tx_lo_error) + поле «TO Error (Hz)» на странице QO-100. Сам транспондер-offset держим стандартным (8089.5 МГц), уход тракта правим поправкой; ActiveTXFreqHz учитывает её, подсказка внизу страницы считает TX-частоту уже с ней. - UI-фиксы вкладки Calibration: длинные подсказки переведены на несколько строк (WordWrap — TLabel по умолчанию режется по краю группы), заголовки таблиц укорочены и не заезжают под кнопку «= S-meter»; RelayoutCalTab сбрасывает прокрутку страницы перед перекладкой (SetBounds в TScrollBox задаёт координаты относительно прокрученного клиента). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
266 lines
12 KiB
ObjectPascal
266 lines
12 KiB
ObjectPascal
unit IIOBindings;
|
||
|
||
{
|
||
Минимальный биндинг к libiio (классический API 0.x; в системе 0.26) с
|
||
ДИНАМИЧЕСКОЙ загрузкой. Если libiio нет — IIOLoad возвращает False, и
|
||
Pluto-бэкенд просто не участвует в discovery (приложение работает с HPSDR).
|
||
|
||
Загружаем libiio.so / libiio.so.0 (Linux), iio.dll (Windows),
|
||
libiio.dylib (macOS). Имена символов одинаковые на всех платформах.
|
||
|
||
Покрыты функции для: scan (discovery), context create/destroy, поиск
|
||
device/channel, чтение/запись атрибутов, буферы RX/TX. Этого набора хватает
|
||
на фазы 1–4 (discovery → RX → управление → TX).
|
||
}
|
||
|
||
{$IFDEF FPC}
|
||
{$MODE Delphi}
|
||
{$ENDIF}
|
||
|
||
interface
|
||
|
||
uses
|
||
dynlibs, ctypes, SysUtils;
|
||
|
||
type
|
||
// Непрозрачные указатели libiio
|
||
Piio_scan_context = Pointer;
|
||
Piio_context_info = Pointer;
|
||
PPiio_context_info = ^Piio_context_info; // struct iio_context_info **
|
||
Piio_context = Pointer;
|
||
Piio_device = Pointer;
|
||
Piio_channel = Pointer;
|
||
Piio_buffer = Pointer;
|
||
|
||
var
|
||
// ---- Scan / discovery ----
|
||
iio_create_scan_context: function(backend: PAnsiChar; flags: cuint): Piio_scan_context; cdecl;
|
||
iio_scan_context_destroy: procedure(ctx: Piio_scan_context); cdecl;
|
||
iio_scan_context_get_info_list: function(ctx: Piio_scan_context;
|
||
var info: PPiio_context_info): ptrint; cdecl;
|
||
iio_context_info_list_free: procedure(info: PPiio_context_info); cdecl;
|
||
iio_context_info_get_description: function(info: Piio_context_info): PAnsiChar; cdecl;
|
||
iio_context_info_get_uri: function(info: Piio_context_info): PAnsiChar; cdecl;
|
||
|
||
// ---- Context ----
|
||
iio_create_context_from_uri: function(uri: PAnsiChar): Piio_context; cdecl;
|
||
iio_context_destroy: procedure(ctx: Piio_context); cdecl;
|
||
iio_context_get_name: function(ctx: Piio_context): PAnsiChar; cdecl;
|
||
iio_context_find_device: function(ctx: Piio_context; name: PAnsiChar): Piio_device; cdecl;
|
||
iio_context_get_attr_value: function(ctx: Piio_context; name: PAnsiChar): PAnsiChar; cdecl;
|
||
|
||
// ---- Device / channel ----
|
||
// output — C '_Bool' (1 байт). Передаём явный байт 0/1 (ByteBool/True давал
|
||
// некорректное значение → output-каналы не находились).
|
||
iio_device_find_channel: function(dev: Piio_device; name: PAnsiChar;
|
||
output: cuchar): Piio_channel; cdecl;
|
||
iio_device_set_kernel_buffers_count: function(dev: Piio_device; nb: cuint): cint; cdecl;
|
||
iio_channel_enable: procedure(chn: Piio_channel); cdecl;
|
||
iio_channel_disable: procedure(chn: Piio_channel); cdecl;
|
||
|
||
// ---- Атрибуты ----
|
||
iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
||
out val: clonglong): cint; cdecl;
|
||
iio_channel_attr_read_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||
out val: cdouble): cint; cdecl;
|
||
iio_channel_attr_write_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
||
val: clonglong): cint; cdecl;
|
||
iio_channel_attr_write: function(chn: Piio_channel; attr: PAnsiChar;
|
||
src: PAnsiChar): ptrint; cdecl;
|
||
iio_channel_attr_write_bool: function(chn: Piio_channel; attr: PAnsiChar;
|
||
val: ByteBool): cint; cdecl;
|
||
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||
val: cdouble): cint; cdecl;
|
||
|
||
// ---- Атрибуты устройства (не канальные) ----
|
||
// ad9361-phy: 'xo_correction' — фактическая частота опорного XO в Гц, какой её
|
||
// считает драйвер. Правка = частотная калибровка всего чипа (RX и TX LO).
|
||
iio_device_attr_read_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||
out val: clonglong): cint; cdecl;
|
||
iio_device_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||
val: clonglong): cint; cdecl;
|
||
|
||
// ---- Debug-атрибуты устройства (debugfs) ----
|
||
// Нужны для выбора антенного разъёма у AD936x в режиме 1R1T:
|
||
// 'adi,1rx-1tx-mode-use-rx-num'/'-tx-num' (какой физический канал кормит тракт)
|
||
// + 'initialize' (драйвер заново прогоняет ad9361_setup из pdata).
|
||
iio_device_debug_attr_read: function(dev: Piio_device; attr: PAnsiChar;
|
||
dst: PAnsiChar; len: csize_t): ptrint; cdecl;
|
||
iio_device_debug_attr_write: function(dev: Piio_device; attr: PAnsiChar;
|
||
src: PAnsiChar): ptrint; cdecl;
|
||
iio_device_debug_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||
val: clonglong): cint; cdecl;
|
||
// Прямой доступ к регистрам (debugfs direct_reg_access) — быстрое переключение
|
||
// антенного мультиплексора AD9361 (биты RX1/RX2 и TX1/TX2 регистров 0x3/0x2).
|
||
iio_device_reg_read: function(dev: Piio_device; address: cuint32;
|
||
out value: cuint32): cint; cdecl;
|
||
iio_device_reg_write: function(dev: Piio_device; address: cuint32;
|
||
value: cuint32): cint; cdecl;
|
||
|
||
// ---- Буферы (RX refill / TX push) ----
|
||
iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
|
||
cyclic: ByteBool): Piio_buffer; cdecl;
|
||
iio_buffer_destroy: procedure(buf: Piio_buffer); cdecl;
|
||
iio_buffer_cancel: procedure(buf: Piio_buffer); cdecl; // разблокирует refill/push
|
||
iio_buffer_refill: function(buf: Piio_buffer): ptrint; cdecl;
|
||
iio_buffer_push: function(buf: Piio_buffer): ptrint; cdecl;
|
||
iio_buffer_first: function(buf: Piio_buffer; chn: Piio_channel): Pointer; cdecl;
|
||
iio_buffer_step: function(buf: Piio_buffer): ptrint; cdecl;
|
||
iio_buffer_end: function(buf: Piio_buffer): Pointer; cdecl;
|
||
|
||
// ---- Утилиты ----
|
||
iio_strerror: procedure(err: cint; dst: PAnsiChar; len: csize_t); cdecl;
|
||
|
||
// ---- libad9361 (опционально) ----
|
||
// ad9361_set_bb_rate: настраивает BBPLL + ЗАГРУЖАЕТ FIR-таблицу под частоту.
|
||
// Без него прямая запись sampling_frequency на холодную даёт мусор (битый FIR).
|
||
// nil если libad9361 не загружена.
|
||
ad9361_set_bb_rate: function(dev: Piio_device; rate: culong): cint; cdecl;
|
||
ad9361_set_trx_fir_enable: function(dev: Piio_device; enable: cint): cint; cdecl;
|
||
|
||
// Загружает libiio (один раз). True — готово к использованию. Идемпотентно.
|
||
function IIOLoad: Boolean;
|
||
// True, если libiio загружена и символы разрешены.
|
||
function IIOLoaded: Boolean;
|
||
// Расшифровка кода ошибки libiio (err — отрицательный errno).
|
||
function IIOStrError(err: cint): string;
|
||
|
||
implementation
|
||
|
||
var
|
||
GLib: TLibHandle = NilHandle;
|
||
GLibAD: TLibHandle = NilHandle; // libad9361 (опционально)
|
||
GLoaded: Boolean = False;
|
||
|
||
const
|
||
{$IFDEF WINDOWS}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.dll', 'iio.dll');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.dll', 'ad9361.dll');
|
||
{$ELSE}
|
||
{$IFDEF DARWIN}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.dylib', 'libiio.1.dylib');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.dylib', 'libad9361.0.dylib');
|
||
{$ELSE}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.so', 'libiio.so.0');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.so', 'libad9361.so.0');
|
||
{$ENDIF}
|
||
{$ENDIF}
|
||
|
||
function Sym(const Name: string): Pointer;
|
||
begin
|
||
Result := GetProcedureAddress(GLib, Name);
|
||
if Result = nil then
|
||
raise Exception.CreateFmt('libiio: symbol "%s" not found', [Name]);
|
||
end;
|
||
|
||
function IIOLoaded: Boolean;
|
||
begin
|
||
Result := GLoaded;
|
||
end;
|
||
|
||
function IIOLoad: Boolean;
|
||
var
|
||
I: Integer;
|
||
begin
|
||
if GLoaded then Exit(True);
|
||
if GLib = NilHandle then
|
||
for I := 0 to High(IIO_LIBS) do
|
||
begin
|
||
GLib := LoadLibrary(IIO_LIBS[I]);
|
||
if GLib <> NilHandle then Break;
|
||
end;
|
||
if GLib = NilHandle then Exit(False);
|
||
|
||
try
|
||
iio_create_scan_context := Sym('iio_create_scan_context');
|
||
iio_scan_context_destroy := Sym('iio_scan_context_destroy');
|
||
iio_scan_context_get_info_list := Sym('iio_scan_context_get_info_list');
|
||
iio_context_info_list_free := Sym('iio_context_info_list_free');
|
||
iio_context_info_get_description := Sym('iio_context_info_get_description');
|
||
iio_context_info_get_uri := Sym('iio_context_info_get_uri');
|
||
iio_create_context_from_uri := Sym('iio_create_context_from_uri');
|
||
iio_context_destroy := Sym('iio_context_destroy');
|
||
iio_context_get_name := Sym('iio_context_get_name');
|
||
iio_context_find_device := Sym('iio_context_find_device');
|
||
iio_context_get_attr_value := Sym('iio_context_get_attr_value');
|
||
iio_device_find_channel := Sym('iio_device_find_channel');
|
||
iio_device_set_kernel_buffers_count := Sym('iio_device_set_kernel_buffers_count');
|
||
iio_channel_enable := Sym('iio_channel_enable');
|
||
iio_channel_disable := Sym('iio_channel_disable');
|
||
iio_channel_attr_read_longlong := Sym('iio_channel_attr_read_longlong');
|
||
iio_channel_attr_read_double := Sym('iio_channel_attr_read_double');
|
||
iio_channel_attr_write_longlong := Sym('iio_channel_attr_write_longlong');
|
||
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
||
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
||
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
||
iio_device_attr_read_longlong := Sym('iio_device_attr_read_longlong');
|
||
iio_device_attr_write_longlong := Sym('iio_device_attr_write_longlong');
|
||
iio_device_debug_attr_read := Sym('iio_device_debug_attr_read');
|
||
iio_device_debug_attr_write := Sym('iio_device_debug_attr_write');
|
||
iio_device_debug_attr_write_longlong := Sym('iio_device_debug_attr_write_longlong');
|
||
iio_device_reg_read := Sym('iio_device_reg_read');
|
||
iio_device_reg_write := Sym('iio_device_reg_write');
|
||
iio_device_create_buffer := Sym('iio_device_create_buffer');
|
||
iio_buffer_destroy := Sym('iio_buffer_destroy');
|
||
iio_buffer_cancel := Sym('iio_buffer_cancel');
|
||
iio_buffer_refill := Sym('iio_buffer_refill');
|
||
iio_buffer_push := Sym('iio_buffer_push');
|
||
iio_buffer_first := Sym('iio_buffer_first');
|
||
iio_buffer_step := Sym('iio_buffer_step');
|
||
iio_buffer_end := Sym('iio_buffer_end');
|
||
iio_strerror := Sym('iio_strerror');
|
||
|
||
// libad9361 — опционально (ad9361_set_bb_rate грузит FIR). Отсутствие не
|
||
// фатально: ApplySampleRate откатится на прямую запись sampling_frequency.
|
||
if GLibAD = NilHandle then
|
||
for I := 0 to High(AD_LIBS) do
|
||
begin
|
||
GLibAD := LoadLibrary(AD_LIBS[I]);
|
||
if GLibAD <> NilHandle then Break;
|
||
end;
|
||
if GLibAD <> NilHandle then
|
||
begin
|
||
ad9361_set_bb_rate := GetProcedureAddress(GLibAD, 'ad9361_set_bb_rate');
|
||
ad9361_set_trx_fir_enable := GetProcedureAddress(GLibAD, 'ad9361_set_trx_fir_enable');
|
||
end;
|
||
|
||
GLoaded := True;
|
||
except
|
||
on E: Exception do
|
||
begin
|
||
UnloadLibrary(GLib);
|
||
GLib := NilHandle;
|
||
GLoaded := False;
|
||
end;
|
||
end;
|
||
Result := GLoaded;
|
||
end;
|
||
|
||
function IIOStrError(err: cint): string;
|
||
var
|
||
Buf: array[0..255] of AnsiChar;
|
||
begin
|
||
if GLoaded and Assigned(iio_strerror) then
|
||
begin
|
||
FillChar(Buf, SizeOf(Buf), 0);
|
||
iio_strerror(Abs(err), @Buf[0], SizeOf(Buf));
|
||
Result := string(PAnsiChar(@Buf[0]));
|
||
end
|
||
else
|
||
Result := Format('iio error %d', [err]);
|
||
end;
|
||
|
||
finalization
|
||
if GLibAD <> NilHandle then
|
||
begin
|
||
UnloadLibrary(GLibAD);
|
||
GLibAD := NilHandle;
|
||
end;
|
||
if GLib <> NilHandle then
|
||
begin
|
||
UnloadLibrary(GLib);
|
||
GLib := NilHandle;
|
||
GLoaded := False;
|
||
end;
|
||
end.
|