Files
ewsdr/IIOBindings.pas
ew8bakandClaude Opus 4.8 9dd38e1dd3 feat(pluto): выбор антенного разъёма RX/TX на диапазон (LibreSDR/AntSDR)
Второй разъём у AD936x-плат — это второй КАНАЛ чипа (не порт B первого), и в
1R1T-образе он выбирается мультиплексором AD9361: debug-параметры
adi,1rx-1tx-mode-use-rx-num/-tx-num + биты RX1/RX2, TX1/TX2 регистров 0x3/0x2
(как switch_rfinput.sh/switch_rfoutput.sh прошивки). rf_port_select тут ни при
чём — прошивка его к тому же блокирует.

- IIOBindings: debug-атрибуты + прямой доступ к регистрам.
- PlutoBackend: детект второго разъёма (2R2T-каналы либо мультиплексор + модель
  платы), SetAntPorts(RxPort,TxPort), ApplyRxMux/ApplyTxMux; Caps.HasAntSwitch.
  ⚠ApplyGain(Force): драйвер пропускает запись gain_control_mode, если считает
  режим неизменным, — а у нового канала AGC-регистры не запрограммированы (в
  железе крутится auto-AGC, hardwaregain отвергается EOPNOTSUPP). Форсируем
  перепрог через другой режим после смены разъёма и после ad9361_set_bb_rate.
- RadioBackend: TBackendCaps.HasAntSwitch + virtual SetAntPorts (HPSDR — no-op,
  там антеннами рулит Alex).
- Settings: секция ant936x (разъём RX/TX на диапазон и на слот трансвертера);
  hw-AGC (режим + manual gain) переехал в TBandSettings/TXvtrEntry — теперь он
  свой на каждом диапазоне и трансвертере.
- RadioController: FAnt936x + ApplyAntPorts (RestoreBand/ActivateXvtr/
  StartRunning); ApplyBandDSP восстанавливает hw-AGC диапазона.
- SettingsForm: вкладка Antenna — Alex-таблица (openHPSDR) либо таблица
  разъёмов AD936x по диапазонам/трансвертерам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 21:26:18 +03:00

256 lines
11 KiB
ObjectPascal
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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. Этого набора хватает
на фазы 14 (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;
// ---- 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_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.