Compare commits

...

7 Commits

Author SHA1 Message Date
Maxim
ac0e3944ab СКВ 2026-06-04 09:05:21 +03:00
Maxim
3cceda32f4 Readme 2026-06-03 14:36:15 +03:00
Maxim
24b7cc402e Рефакторинг 2026-06-03 14:23:30 +03:00
Maxim
9d6452c860 Доработка синхронизации 2026-06-03 13:28:29 +03:00
Maxim
b00af27058 Работающая версия с PLuto + liquidDSP 2026-05-28 09:08:51 +03:00
Maxim
8e296daacc Дорабтка c Pluto SDR 2026-05-27 14:13:57 +03:00
Maxim
941a7cf6e6 Дорабтка c HackRF 2026-05-20 11:30:45 +03:00
12 changed files with 1345 additions and 158 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ progress-server
# Тестовые бинарники
*.test
test.jpg
# Результаты тестов с покрытием
*.out

320
SDR/README.md Normal file
View File

@@ -0,0 +1,320 @@
# OFDM Radio Link — PlutoSDR
Система цифровой радиосвязи на базе PlutoSDR с использованием OFDM-модуляции и помехоустойчивого кодирования Рида-Соломона RS(255,223).
## Оглавление
- [Возможности](#возможности)
- [Требования](#требования)
- [Структура проекта](#структура-проекта)
- [Быстрый старт](#быстрый-старт)
- [Сборка](#сборка)
- [Использование](#использование)
- [Параметры командной строки](#параметры-командной-строки)
- [Формат кадра](#формат-кадра)
- [Диагностика](#диагностика)
- [Устранение неполадок](#устранение-неполадок)
- [Лицензия](#лицензия)
## Возможности
- **OFDM-модуляция**: 64 поднесущих, циклический префикс 16, сглаживающее окно 4
- **Помехоустойчивое кодирование**: Рида-Соломона RS(255,223) с CRC32
- **Частотная коррекция**: адаптивная компенсация CFO через NCO с экспоненциальным фильтром (α=0.95)
- **Ручное управление усилением**: фиксированное усиление для максимальной стабильности
- **Потоковая передача**: работа через stdin/stdout для конвейерной обработки
- **Мониторинг в реальном времени**: RSSI, EVM, CFO, статистика кадров
- **Гибкая настройка**: частота, полоса, усиление, амплитуда, паузы между кадрами
## Требования
### Аппаратное обеспечение
- 2× PlutoSDR (один на передачу, один на приём) или 1× для тестирования в петле
- Raspberry Pi (рекомендуется) или любой Linux-компьютер
- Антенны/аттенюаторы для RF-тракта
### Программное обеспечение
```bash
# Установка зависимостей
sudo apt update
sudo apt install -y build-essential python3 cmake libfftw3-dev
# libiio (для работы с PlutoSDR)
sudo apt install -y libiio-dev
# liquid-dsp (OFDM и FEC)
git clone https://github.com/jgaeddert/liquid-dsp.git
cd liquid-dsp
./bootstrap.sh
./configure
make -j4
sudo make install
sudo ldconfig
```
## Структура проекта
```
pluto/
├── common.h # Общие определения и прототипы
├── common.c # Реализация общих функций
├── transmitter.c # Передатчик
├── receiver.c # Приёмник с частотной коррекцией
├── Makefile # Система сборки
├── README.md # Этот файл
└── test_input.bin # Тестовый файл (создаётся make test)
```
## Быстрый старт
```bash
# 1. Сборка
make
# 2. Создание тестового файла (1 МБ)
make test
# 3. Запуск приёмника (на одном устройстве)
./receiver -f 1265000000 -r 3840000 -b 2000000 -c rs8 > output.bin
# 4. Запуск передатчика (на другом устройстве)
cat test_input.bin | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -c rs8
# 5. Проверка результата
md5sum test_input.bin output.bin
```
## Сборка
```bash
# Сборка всего проекта
make
# Сборка только передатчика (если есть transmitter.c)
make transmitter
# Сборка только приёмника (если есть receiver.c)
make receiver
# Создание тестового файла 1 МБ
make test
# Очистка
make clean
# Справка
make help
```
**Флаги компиляции:**
- `-Wall -O2` — все предупреждения и оптимизация
- `-liio` — библиотека libiio для PlutoSDR
- `-lliquid` — liquid-dsp (OFDM, FEC, NCO)
- `-lfftw3f` — FFTW для преобразований Фурье
- `-lm -lpthread` — математика и потоки
## Использование
### Передатчик
```bash
# Базовая передача тестового файла
cat test_input.bin | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -c rs8
# Передача случайных данных
cat /dev/urandom | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3
# Высокоскоростная передача без пауз
cat data.bin | ./transmitter -f 2400000000 -r 7680000 -b 6000000 -g 0 -a 0.5 -p 0
# Передача текста
echo "Hello, OFDM World!" | ./transmitter -f 1265000000 -r 3840000 -b 2000000
```
### Приёмник
```bash
# Базовый приём с декодированием FEC
./receiver -f 1265000000 -r 3840000 -b 2000000 -c rs8 > output.bin
# Приём без FEC (выше скорость, но нет исправления ошибок)
./receiver -f 1265000000 -r 3840000 -b 2000000 -c none > output.bin
# Приём с сохранением лога статистики
./receiver -f 1265000000 -r 3840000 -b 2000000 -c rs8 > output.bin 2> stats.log
# Мониторинг статистики в реальном времени
watch -n 1 'tail -1 stats.log'
```
### Тестирование на одном устройстве (петля)
```bash
# Терминал 1: Приёмник
./receiver -f 2400000000 -r 3840000 -b 2000000 -c rs8 > output.bin
# Терминал 2: Передатчик
cat test_input.bin | ./transmitter -f 2400000000 -r 3840000 -b 2000000 -g -40 -c rs8
```
## Параметры командной строки
### Общие параметры (для TX и RX)
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `-f ЧАСТОТА` | Несущая частота в Гц | 1265000000 (1265 МГц) |
| `-r СКОРОСТЬ` | Частота дискретизации в Гц | 3840000 (3.84 МГц) |
| `-b ПОЛОСА` | Полоса пропускания в Гц | 3000000 (3 МГц) |
| `-u URI` | Адрес PlutoSDR | ip:192.168.2.1 (RX) / ip:192.168.3.1 (TX) |
| `-c РЕЖИМ` | FEC: `rs8` или `none` | rs8 |
| `-h` | Показать справку | — |
### Специфичные параметры передатчика
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `-g УСИЛЕНИЕ` | Усиление TX в дБ | -30.0 |
| `-a АМПЛИТУДА` | Амплитуда сигнала (0..1) | 0.2 |
| `-p ПАУЗА` | Пауза между кадрами в мкс | 2000 |
### Специфичные параметры приёмника
| Параметр | Значение | Примечание |
|----------|----------|------------|
| Усиление | 50 дБ | Фиксированное, ручной режим |
| CFO фильтр | α=0.95 | Экспоненциальное сглаживание |
## Формат кадра
```
┌─────────────────────────────────────────────────────────────────┐
│ OFDM КАДР │
├──────────┬──────────────────────────────────────────────────────┤
│ Заголовок│ Полезная нагрузка │
│ 12 байт │ до 1024 байт │
└──────────┴──────────────────────────────────────────────────────┘
Заголовок (12 байт):
┌───────┬───────┬──────────────────┬───────────────┬───────────────┬──────────────┬──────────┐
│ Байты │ 0-1 │ 2-5 │ 6-7 │ 8-9 │ 10 │ 11 │
├───────┼───────┼──────────────────┼───────────────┼───────────────┼──────────────┼──────────┤
│ Знач. │ 0xF0 │ Порядковый номер │ Длина исходных│ Кол-во блоков │ Последний │ Резерв │
│ │ 0xAA │ кадра (32 бита) │ данных (16 бит)│ FEC (16 бит) │ блок RS │ │
└───────┴───────┴──────────────────┴───────────────┴───────────────┴──────────────┴──────────┘
```
## Диагностика
### Вывод приёмника в реальном времени
```
Кадры | Загол. | CRC | RS испр | Вывод | RSSI | EVM | CFO
--------+--------+------+---------+--------+-------+-------+----------
1234 | 1200 | 1180 | 15 | 1195 | -45 | 2.3 | 0.0015
```
**Расшифровка:**
- **Кадры** — всего обнаружено кадров синхронизатором
- **Загол.** — количество корректно декодированных заголовков
- **CRC** — кадры с корректной контрольной суммой
- **RS испр** — кадры, исправленные кодом Рида-Соломона
- **Вывод** — успешно выведено кадров в stdout
- **RSSI** — уровень сигнала в дБм (чем ближе к 0, тем лучше)
- **EVM** — вектор ошибки в % (чем меньше, тем лучше)
- **CFO** — оценка частотного сдвига в рад/семпл
### Итоговая статистика
```
═══════════════════════════════════════════════════
ПРИЁМ ЗАВЕРШЁН
═══════════════════════════════════════════════════
Обнаружено кадров: 1024
Корректных заголовков: 1024 (100.0%)
Корректных CRC: 1024 (100.0%)
Исправлено RS: 0
Успешно выведено: 1024 кадров
Всего принято данных: 1048576 байт (1.00 МБ)
Финальная оценка CFO: 0.0012 рад/семпл
═══════════════════════════════════════════════════
```
## Устранение неполадок
### Ошибка подключения к PlutoSDR
```bash
# Проверка подключения
iio_info -s
# Пинг устройства
ping 192.168.2.1
# Проверка интерфейса
ifconfig eth1
```
### Нет приёма сигнала
1. **Проверьте частоту** — убедитесь, что TX и RX на одной частоте
2. **Увеличьте усиление TX** — попробуйте `-g 0` вместо `-g -30`
3. **Уменьшите расстояние** — или используйте проводное соединение
4. **Проверьте антенны** — убедитесь, что они подключены к правильным портам
5. **Используйте петлю** — подключите TX напрямую к RX через аттенюатор 30-40 дБ
### Высокий EVM / много ошибок
1. **Уменьшите полосу**`-b 1500000` вместо `-b 3000000`
2. **Увеличьте усиление RX** — отредактируйте `RX_GAIN` в `receiver.c`
3. **Добавьте паузы**`-p 5000` для стабилизации
4. **Уменьшите амплитуду TX**`-a 0.15` для снижения искажений
5. **Проверьте CFO** — значение > 0.1 требует настройки частоты
### Ошибка сегментирования (Segmentation Fault)
1. **Проверьте версию liquid-dsp** — требуется >= 1.3.0
2. **Пересоберите с отладкой**:
```bash
gcc -g -O0 -o receiver_debug receiver.c common.c -liio -lliquid -lfftw3f -lm -lpthread
gdb ./receiver_debug
run -f 1265000000 -r 3840000 -b 2000000 -c rs8
bt
```
3. **Проверьте права доступа к USB**:
```bash
sudo chmod 666 /dev/iio*
```
### Предупреждения компиляции
Предупреждения типа `-Wformat` исправлены добавлением суффикса `LL` к константам:
```c
#define DEFAULT_FREQ 1265000000LL // было 1265000000
```
## Оптимальные параметры для разных сценариев
### Максимальная скорость
```bash
# TX
cat data.bin | ./transmitter -f 2400000000 -r 7680000 -b 6000000 -g 0 -a 0.5 -p 0
# RX
./receiver -f 2400000000 -r 7680000 -b 6000000 -c none > output.bin
```
### Максимальная надёжность
```bash
# TX
cat data.bin | ./transmitter -f 1265000000 -r 3840000 -b 1500000 -g -10 -a 0.4 -p 5000 -c rs8
# RX
./receiver -f 1265000000 -r 3840000 -b 1500000 -c rs8 > output.bin
```
## Лицензия
Данный проект распространяется под лицензией MIT. Используйте на свой страх и риск.

106
SDR/common.c Normal file
View File

@@ -0,0 +1,106 @@
/**
* common.c — Реализация общих функций
*/
#include "common.h"
volatile sig_atomic_t stop = 0;
static void sigint_handler(int s) {
(void)s;
stop = 1;
}
void setup_signal_handlers(void) {
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
signal(SIGPIPE, SIG_IGN);
}
void print_usage(const char *prog_name, const char *mode) {
fprintf(stderr, "═══════════════════════════════════════════════════\n");
fprintf(stderr, "OFDM %s — PlutoSDR Radio Link\n", mode);
fprintf(stderr, "═══════════════════════════════════════════════════\n");
fprintf(stderr, "Использование: %s [ОПЦИИ]\n\n", prog_name);
fprintf(stderr, "ОБЩИЕ ОПЦИИ:\n");
fprintf(stderr, " -f ЧАСТОТА Частота несущей в Гц (по умолчанию: %lld)\n", DEFAULT_FREQ);
fprintf(stderr, " -r СКОРОСТЬ Частота дискретизации (по умолчанию: %lld)\n", DEFAULT_RATE);
fprintf(stderr, " -b ПОЛОСА Полоса пропускания (по умолчанию: %lld)\n", DEFAULT_BW);
fprintf(stderr, " -u URI URI PlutoSDR\n");
fprintf(stderr, " -c РЕЖИМ FEC: rs8 или none (по умолчанию: rs8)\n");
fprintf(stderr, " -h Показать эту справку\n");
}
void print_config(const char *mode, long long freq, long long rate,
long long bw, int fec_on) {
fprintf(stderr, "═══════════════════════════════════════════════════\n");
fprintf(stderr, "КОНФИГУРАЦИЯ %s\n", mode);
fprintf(stderr, "Частота: %.1f МГц\n", freq / 1e6);
fprintf(stderr, "Дискрет.: %.2f МГц\n", rate / 1e6);
fprintf(stderr, "Полоса: %.2f МГц\n", bw / 1e6);
fprintf(stderr, "OFDM: M=%d, CP=%d, Taper=%d\n", OFDM_M, OFDM_CP, OFDM_TAPER);
fprintf(stderr, "FEC: %s\n", fec_on ? "RS(255,223)" : "ОТКЛЮЧЕН");
}
int pluto_init(const char *uri, struct iio_context **ctx,
struct iio_device **phy, struct iio_device **dev, int is_tx) {
*ctx = iio_create_context_from_uri(uri);
if (!*ctx) {
LOG_ERROR("Не удалось подключиться к PlutoSDR по адресу %s", uri);
return -1;
}
*phy = iio_context_find_device(*ctx, "ad9361-phy");
*dev = iio_context_find_device(*ctx,
is_tx ? "cf-ad9361-dds-core-lpc" : "cf-ad9361-lpc");
if (!*phy || !*dev) {
LOG_ERROR("Радиоустройства не найдены в контексте");
iio_context_destroy(*ctx);
return -1;
}
return 0;
}
void pluto_configure(struct iio_device *phy, long long freq, long long rate,
long long bw, double gain, int is_tx) {
if (is_tx) {
// Настройка передатчика
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "altvoltage1", true),
"frequency", freq);
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", true),
"sampling_frequency", rate);
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", true),
"rf_bandwidth", bw);
iio_channel_attr_write(
iio_device_find_channel(phy, "voltage0", true),
"rf_port_select", "A");
iio_channel_attr_write_double(
iio_device_find_channel(phy, "voltage0", true),
"hardwaregain", gain);
} else {
// Настройка приёмника
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "altvoltage0", true),
"frequency", freq);
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", false),
"sampling_frequency", rate);
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", false),
"rf_bandwidth", bw);
iio_channel_attr_write(
iio_device_find_channel(phy, "voltage0", false),
"gain_control_mode", "manual");
iio_channel_attr_write_double(
iio_device_find_channel(phy, "voltage0", false),
"hardwaregain", gain);
iio_channel_attr_write(
iio_device_find_channel(phy, "voltage0", false),
"rf_port_select", "A_BALANCED");
}
}

61
SDR/common.h Normal file
View File

@@ -0,0 +1,61 @@
/**
* common.h — Общие определения и конфигурация для приёмника и передатчика
* OFDM + RS(255,223) FEC + PlutoSDR
*/
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#include <getopt.h>
#include <time.h>
#include <unistd.h>
#include <iio.h>
#include <liquid/liquid.h>
// Параметры OFDM-модуляции
#define OFDM_M 64
#define OFDM_CP 16
#define OFDM_TAPER 4
// Параметры Рида-Соломона
#define RS_DATA 223
#define RS_ENC 255
// Параметры радиотракта по умолчанию
#define DEFAULT_FREQ 1265000000LL
#define DEFAULT_RATE 3840000LL
#define DEFAULT_BW 3000000LL
// Адреса PlutoSDR
#define TX_URI_DEFAULT "ip:192.168.3.1" // адрес Pluto передатчика
#define RX_URI_DEFAULT "ip:192.168.2.1" // адрес Pluto приемника
// Формат заголовка кадра
#define HDR_SIGNATURE_0 0xF0
#define HDR_SIGNATURE_1 0xAA
#define HDR_SIZE 12
// Макросы логирования
#define LOG_ERROR(fmt, ...) fprintf(stderr, "[ОШИБКА] " fmt "\n", ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) fprintf(stderr, "[INFO] " fmt "\n", ##__VA_ARGS__)
// Глобальный флаг завершения
extern volatile sig_atomic_t stop;
// Прототипы общих функций
void setup_signal_handlers(void);
void print_usage(const char *prog_name, const char *mode);
void print_config(const char *mode, long long freq, long long rate,
long long bw, int fec_on);
int pluto_init(const char *uri, struct iio_context **ctx,
struct iio_device **phy, struct iio_device **dev, int is_tx);
void pluto_configure(struct iio_device *phy, long long freq, long long rate,
long long bw, double gain, int is_tx);
#endif

47
SDR/makefile Normal file
View File

@@ -0,0 +1,47 @@
# Makefile для сборки OFDM-приёмника и передатчика для PlutoSDR
CC = gcc
CFLAGS = -Wall -O2
LDFLAGS = -liio -lliquid -lfftw3f -lm -lpthread
TX_BIN = transmitter
RX_BIN = receiver
TX_SRC = transmitter.c
RX_SRC = receiver.c
TX_EXISTS := $(shell test -f $(TX_SRC) && echo 1 || echo 0)
RX_EXISTS := $(shell test -f $(RX_SRC) && echo 1 || echo 0)
ifeq ($(TX_EXISTS),1)
TARGETS += $(TX_BIN)
endif
ifeq ($(RX_EXISTS),1)
TARGETS += $(RX_BIN)
endif
all: $(TARGETS)
@echo "Готово: $(TARGETS)"
$(TX_BIN): $(TX_SRC) common.c common.h
$(CC) $(CFLAGS) -o $@ $(TX_SRC) common.c $(LDFLAGS)
@echo "$(TX_BIN) собран"
$(RX_BIN): $(RX_SRC) common.c common.h
$(CC) $(CFLAGS) -o $@ $(RX_SRC) common.c $(LDFLAGS)
@echo "$(RX_BIN) собран"
test:
python3 -c "import struct; f=open('test_input.bin','wb'); [f.write(struct.pack('B', i%256)) for i in range(1048576)]; f.close()"
@ls -lh test_input.bin
clean:
rm -f $(TX_BIN) $(RX_BIN) *.o test_input.bin output.bin
help:
@echo "Цели:"
@echo " make — сборка всего, что есть"
@echo " make test — создать тестовый файл 1 МБ"
@echo " make clean — удалить всё собранное"
.PHONY: all test clean help

206
SDR/reciever.c Normal file
View File

@@ -0,0 +1,206 @@
/**
* receiver.c — OFDM-приёмник с RS(255,223) FEC и частотной коррекцией
*/
#include "common.h"
#define BUF_SAMPLES 32768
#define MAX_FRAME_SAMPLES (1 << 16)
#define RX_GAIN 50.0
#define CFO_FILTER_ALPHA 0.95f
#define CFO_MAX_VALID 0.5f
extern volatile sig_atomic_t stop;
static uint64_t calls = 0, hdr_ok = 0, crc_raw = 0;
static uint64_t rs_saved = 0, crc_final = 0, total_bytes = 0;
static float rssi = -100, evm = 0;
static float cfo_est = 0.0f;
static nco_crcf nco = NULL;
static fec decoder = NULL;
static int fec_on = 0;
static liquid_float_complex frame[MAX_FRAME_SAMPLES];
static int callback(unsigned char *hdr, int hdr_valid,
unsigned char *pay, unsigned int pay_len,
int pay_valid, framesyncstats_s stats,
void *user) {
(void)user;
calls++;
rssi = stats.rssi;
evm = stats.evm;
// Обновляем CFO только если NCO инициализирован
if (nco && fabsf(stats.cfo) < CFO_MAX_VALID) {
cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo;
nco_crcf_set_frequency(nco, cfo_est);
}
if (!hdr_valid) return 0;
hdr_ok++;
if (pay_valid) crc_raw++;
uint16_t original_len = (hdr[6] << 8) | hdr[7];
uint8_t last_block_bytes = hdr[10];
if (fec_on && decoder && pay_len >= RS_ENC) {
size_t nblocks = pay_len / RS_ENC;
if (nblocks == 0 || nblocks > 100) return 0;
uint8_t recovered[4096];
int rs_ok = 1;
for (size_t b = 0; b < nblocks; b++) {
if (fec_decode(decoder, RS_DATA, pay + b * RS_ENC,
recovered + b * RS_DATA) != 0) {
rs_ok = 0; break;
}
}
if (rs_ok) {
if (!pay_valid) rs_saved++;
size_t out_len = (nblocks - 1) * RS_DATA +
(last_block_bytes > 0 ? last_block_bytes : RS_DATA);
if (out_len > nblocks * RS_DATA) out_len = nblocks * RS_DATA;
if (out_len > original_len) out_len = original_len;
if (out_len > 0) {
fwrite(recovered, 1, out_len, stdout);
fflush(stdout);
total_bytes += out_len;
crc_final++;
}
}
} else if (pay_valid && original_len > 0 && original_len <= pay_len) {
fwrite(pay, 1, original_len, stdout);
fflush(stdout);
total_bytes += original_len;
crc_final++;
}
return 0;
}
int main(int argc, char *argv[]) {
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
const char *uri = RX_URI_DEFAULT;
const char *fec_opt = "rs8";
int opt;
while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) {
switch (opt) {
case 'f': freq = atoll(optarg); break;
case 'r': rate = atoll(optarg); break;
case 'b': bw = atoll(optarg); break;
case 'u': uri = optarg; break;
case 'c': fec_opt = optarg; break;
case 'h':
print_usage(argv[0], "ПРИЁМНИК");
fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ RX:\n");
fprintf(stderr, " Усиление фиксированное: %.0f дБ\n", RX_GAIN);
fprintf(stderr, " Частотная коррекция: адаптивная (α=%.2f)\n", CFO_FILTER_ALPHA);
fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
fprintf(stderr, " %s -f %lld -r %lld -b %lld -c rs8 > output.bin\n",
argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW);
return 0;
default: return 1;
}
}
fec_on = !strcmp(fec_opt, "rs8");
setup_signal_handlers();
print_config("ПРИЁМНИК", freq, rate, bw, fec_on);
fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN);
fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA);
fprintf(stderr, "URI: %s\n", uri);
fprintf(stderr, "═══════════════════════════════════════════════════\n");
struct iio_context *ctx = NULL;
struct iio_device *phy = NULL, *rx = NULL;
if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1;
pluto_configure(phy, freq, rate, bw, RX_GAIN, 0);
iio_channel_enable(iio_device_find_channel(rx, "voltage0", false));
iio_channel_enable(iio_device_find_channel(rx, "voltage1", false));
struct iio_buffer *buf = iio_device_create_buffer(rx, BUF_SAMPLES, false);
if (!buf) {
fprintf(stderr, "Ошибка создания буфера приёма\n");
iio_context_destroy(ctx);
return 1;
}
// Инициализация FEC
if (fec_on) {
decoder = fec_create(LIQUID_FEC_RS_M8, NULL);
if (!decoder) {
fprintf(stderr, "Ошибка создания FEC-декодера\n");
}
}
// Инициализация NCO для частотной коррекции
nco = nco_crcf_create(LIQUID_NCO);
if (!nco) {
fprintf(stderr, "Ошибка создания NCO\n");
} else {
nco_crcf_set_frequency(nco, 0.0f);
}
ofdmflexframesync fs = ofdmflexframesync_create(
OFDM_M, OFDM_CP, OFDM_TAPER, NULL, callback, NULL);
if (!fs) {
fprintf(stderr, "Ошибка создания OFDM-синхронизатора\n");
if (decoder) fec_destroy(decoder);
if (nco) nco_crcf_destroy(nco);
iio_buffer_destroy(buf);
iio_context_destroy(ctx);
return 1;
}
fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | Вывод | RSSI | EVM | CFO\n");
fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n");
time_t last = time(NULL);
while (!stop) {
ssize_t n = iio_buffer_refill(buf);
if (n < 0) { usleep(500); continue; }
int16_t *bb = (int16_t *)iio_buffer_start(buf);
int ns = (iio_buffer_end(buf) - iio_buffer_start(buf)) / sizeof(int16_t) / 2;
if (ns > MAX_FRAME_SAMPLES) ns = MAX_FRAME_SAMPLES;
for (int i = 0; i < ns; i++) {
frame[i] = ((float)bb[2*i] / 2048.0f) +
((float)bb[2*i+1] / 2048.0f) * _Complex_I;
if (nco) {
nco_crcf_mix_down(nco, frame[i], &frame[i]);
nco_crcf_step(nco);
}
}
if (nco) nco_crcf_set_phase(nco, 0.0f);
ofdmflexframesync_execute(fs, frame, ns);
if (time(NULL) > last) {
fprintf(stderr, "\r%6llu | %5llu | %4llu | %6llu | %6llu | %+5.0f | %+5.1f | %+8.4f",
(unsigned long long)calls, (unsigned long long)hdr_ok,
(unsigned long long)crc_raw, (unsigned long long)rs_saved,
(unsigned long long)crc_final, rssi, evm, cfo_est);
fflush(stderr);
last = time(NULL);
}
}
fprintf(stderr, "\n\nПРИЁМ ЗАВЕРШЁН\n");
fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | Вывод: %llu | Байт: %llu\n",
(unsigned long long)calls, (unsigned long long)hdr_ok,
(unsigned long long)crc_raw, (unsigned long long)rs_saved,
(unsigned long long)crc_final, (unsigned long long)total_bytes);
if (decoder) fec_destroy(decoder);
if (nco) nco_crcf_destroy(nco);
ofdmflexframesync_destroy(fs);
iio_buffer_destroy(buf);
iio_context_destroy(ctx);
return 0;
}

185
SDR/transmitter.c Normal file
View File

@@ -0,0 +1,185 @@
/**
* transmitter.c — OFDM-передатчик с RS(255,223) FEC для PlutoSDR
*
* ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА:
* cat test.bin | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -p 0 -c rs8
*/
#include "common.h"
// Параметры передатчика по умолчанию
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
#define DEFAULT_AMP 0.2f // Амплитуда сигнала
#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс
#define BUF_SAMPLES 16384 // Размер буфера в отсчётах
#define MAX_PAYLOAD 1024 // Максимальный размер полезной нагрузки
// Внешняя переменная флага завершения (определена в common.c)
extern volatile sig_atomic_t stop;
int main(int argc, char *argv[]) {
// Инициализация параметров
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
double gain = DEFAULT_GAIN;
float amp = DEFAULT_AMP;
const char *uri = TX_URI_DEFAULT;
int pause_us = DEFAULT_PAUSE, use_fec = 1;
// Обработка аргументов командной строки
int opt;
while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:h")) != -1) {
switch (opt) {
case 'f': freq = atoll(optarg); break;
case 'r': rate = atoll(optarg); break;
case 'b': bw = atoll(optarg); break;
case 'g': gain = atof(optarg); break;
case 'a': amp = atof(optarg); break;
case 'p': pause_us = atoi(optarg); break;
case 'u': uri = optarg; break;
case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break;
case 'h':
print_usage(argv[0], "ПЕРЕДАТЧИК");
fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ TX:\n");
fprintf(stderr, " -g УСИЛЕНИЕ Усиление в дБ (по умолчанию: %.0f)\n", DEFAULT_GAIN);
fprintf(stderr, " -a АМПЛИТ. Амплитуда сигнала (по умолчанию: %.2f)\n", DEFAULT_AMP);
fprintf(stderr, " -p ПАУЗА Пауза между кадрами в мкс (по умолчанию: %d)\n", DEFAULT_PAUSE);
fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
fprintf(stderr, " cat test.bin | %s -f %lld -r %lld -b %lld -c rs8 > /dev/null\n",
argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW);
return 0;
default: return 1;
}
}
// Настройка обработчиков сигналов
setup_signal_handlers();
// Вывод конфигурации
print_config("ПЕРЕДАТЧИК", freq, rate, bw, use_fec);
fprintf(stderr, "Усиление: %.0f дБ\n", gain);
fprintf(stderr, "Амплитуда: %.2f\n", amp);
fprintf(stderr, "Пауза: %d мкс\n", pause_us);
fprintf(stderr, "URI: %s\n", uri);
fprintf(stderr, "═══════════════════════════════════════════════════\n");
// Инициализация PlutoSDR
struct iio_context *ctx = NULL;
struct iio_device *phy = NULL, *tx = NULL;
if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1;
// Настройка радиотракта
pluto_configure(phy, freq, rate, bw, gain, 1);
// Включение каналов передатчика
iio_channel_enable(iio_device_find_channel(tx, "voltage0", true));
iio_channel_enable(iio_device_find_channel(tx, "voltage1", true));
// Создание буфера передачи
struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false);
if (!buf) {
LOG_ERROR("Ошибка создания буфера передачи");
iio_context_destroy(ctx);
return 1;
}
// Инициализация FEC и OFDM
fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL;
ofdmflexframegenprops_s fgp;
ofdmflexframegenprops_init_default(&fgp);
fgp.check = LIQUID_CRC_32;
ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp);
// Счётчики статистики
uint64_t frames = 0, bytes_tx = 0;
uint32_t seq = 0;
time_t last = time(NULL);
fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n");
// Основной цикл передачи
while (!stop) {
uint8_t raw[MAX_PAYLOAD];
size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
if (!n) {
if (feof(stdin) || stop) break;
usleep(10000);
continue;
}
uint8_t *tx_payload = NULL;
size_t tx_len = 0;
uint8_t hdr[HDR_SIZE] = {0};
// Кодирование данных
if (enc) {
size_t total_blocks = (n + RS_DATA - 1) / RS_DATA;
tx_len = total_blocks * RS_ENC;
tx_payload = malloc(tx_len);
for (size_t b = 0; b < total_blocks; b++) {
uint8_t blk[RS_DATA] = {0};
size_t bytes = (b == total_blocks - 1) ? n - b * RS_DATA : RS_DATA;
memcpy(blk, raw + b * RS_DATA, bytes);
fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
}
// Формирование заголовка
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF;
hdr[10] = n % RS_DATA; hdr[11] = 0;
} else {
tx_len = n;
tx_payload = malloc(tx_len);
memcpy(tx_payload, raw, n);
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
}
// Сборка OFDM-кадра
ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len);
free(tx_payload);
// Формирование выходного сигнала
int16_t *out = (int16_t *)iio_buffer_start(buf);
memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t));
int idx = 0, done = 0;
float complex symb[OFDM_M + OFDM_CP];
while (!done && (idx + OFDM_M + OFDM_CP) <= BUF_SAMPLES) {
done = ofdmflexframegen_write(fg, symb, OFDM_M + OFDM_CP);
for (int i = 0; i < OFDM_M + OFDM_CP; i++) {
out[2 * idx] = (int16_t)(crealf(symb[i]) * 32767 * amp);
out[2 * idx + 1] = (int16_t)(cimagf(symb[i]) * 32767 * amp);
idx++;
}
}
if (idx > 0) iio_buffer_push(buf);
// Обновление статистики
frames++; bytes_tx += n; seq++;
if (time(NULL) > last) {
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
(unsigned long long)frames, (unsigned long long)bytes_tx);
fflush(stderr);
last = time(NULL);
}
if (pause_us > 0) usleep(pause_us);
}
fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n",
(unsigned long long)frames, (unsigned long long)bytes_tx);
// Освобождение ресурсов
if (enc) fec_destroy(enc);
ofdmflexframegen_destroy(fg);
iio_buffer_destroy(buf);
iio_context_destroy(ctx);
return 0;
}

1
scripts/array.txt Normal file
View File

@@ -0,0 +1 @@
[0xE0, 0x01, 0x4C, 0x88]

15
scripts/emulatohackrf.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Эмулятор GPIO -> stdout (1 мс на байт)
python3 /home/user/temp/golang/scripts/emulator.py \
array.txt \
- \
0.001 \
| pv | # Передача через HackRF
/home/user/hackrf-liquid-dsp/hackrf-frame64-tx \
-x 40 \
-a 0 \
-d a32868dc347c6e47 \
-s 2000000 \
-b 1750000 \
-f 800000000

230
scripts/emulator.py Normal file
View File

@@ -0,0 +1,230 @@
import sys
import time
import ast
import signal
import os
OUT_BUFFER_LEN = 32
RECONNECT_DELAY = 0.5
REPEAT_COUNT = 100 # повтор
DEFAULT_SEND_DELAY = 0.1 # по умолчанию (10 байт/с)
stop_flag = False
# ================= SIGNALS =================
def handle_sigint(signum, frame):
global stop_flag
stop_flag = True
signal.signal(signal.SIGINT, handle_sigint)
signal.signal(signal.SIGTERM, handle_sigint)
# ================= UI =================
last_status = ""
def status(msg):
global last_status
if msg != last_status:
sys.stderr.write("\r" + msg + " " * 20)
sys.stderr.flush()
last_status = msg
def status_clear():
sys.stderr.write("\r" + " " * 80 + "\r")
sys.stderr.flush()
# ================= DATA =================
def load_array(path):
while True:
try:
with open(path, "r") as f:
content = f.read().strip()
data = ast.literal_eval(content)
result = []
for x in data:
if isinstance(x, str):
x = int(x, 0)
result.append(x & 0xFF)
print(f"[emu] Загружено {len(result)} байт", file=sys.stderr)
return result
except Exception as e:
print(f"[emu] Ошибка чтения: {e}", file=sys.stderr)
time.sleep(1)
# ================= PIPE =================
def open_pipe(path):
while not stop_flag:
try:
fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK)
status("[emu] pipe: подключен")
return os.fdopen(fd, "wb", buffering=0)
except OSError:
status("[emu] pipe: нет читателя")
time.sleep(RECONNECT_DELAY)
return None
# ================= ARGS PARSING =================
def parse_args():
"""
Разбирает аргументы командной строки.
Использование:
python3 emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]
Где:
задержка — float, секунд между байтами (по умолчанию DEFAULT_SEND_DELAY)
logfile — путь к файлу лога или "-" для stderr
"""
if len(sys.argv) < 3:
print("Использование: python emulator.py array.txt <pipe_path|-> [задержка] [logfile|-]", file=sys.stderr)
sys.exit(1)
array_path = sys.argv[1]
pipe_path = sys.argv[2]
send_delay = DEFAULT_SEND_DELAY
log_file = None
arg_offset = 0
if len(sys.argv) > 3:
try:
send_delay = float(sys.argv[3])
arg_offset = 1
print(f"[emu] Задержка: {send_delay} с (~{1/send_delay:.0f} байт/с)", file=sys.stderr)
except ValueError:
arg_offset = 0
log_idx = 3 + arg_offset
if len(sys.argv) > log_idx:
log_path = sys.argv[log_idx]
if log_path == "-":
log_file = sys.stderr
else:
log_file = open(log_path, "a")
return array_path, pipe_path, send_delay, log_file
# ================= MAIN =================
def main():
array_path, pipe_path, send_delay, log_file = parse_args()
data = load_array(array_path)
log_buffer = []
total_bytes = 0
packet_id = 0
i = 0
repeat = 0
# ===== STDOUT MODE =====
if pipe_path == "-":
pipe = sys.stdout.buffer # бинарный stdout
print(f"[emu] Режим stdout, {len(data)} байт в цикле", file=sys.stderr)
try:
while not stop_flag:
byte = data[i]
pipe.write(bytes([byte]))
pipe.flush()
log_buffer.append(byte)
total_bytes += 1
if len(log_buffer) >= OUT_BUFFER_LEN and log_file:
packet_id += 1
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except KeyboardInterrupt:
pass
except BrokenPipeError:
pass # нормально для pipe
finally:
status_clear()
print("[emu] завершён", file=sys.stderr)
return
# ===== FIFO MODE =====
pipe = None
while not stop_flag:
if pipe is None:
pipe = open_pipe(pipe_path)
if pipe is None:
break
try:
byte = data[i]
pipe.write(bytes([byte]))
log_buffer.append(byte)
total_bytes += 1
if len(log_buffer) >= OUT_BUFFER_LEN and log_file:
packet_id += 1
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except BrokenPipeError:
pipe.close()
pipe = None
status("[emu] pipe: отключён")
except Exception as e:
status(f"[emu] ошибка: {e}")
time.sleep(1)
status_clear()
print("[emu] завершён", file=sys.stderr)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,29 @@
[Unit]
Description=GPIO Monitor Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=user
Group=user
WorkingDirectory=/home/user/temp/golang
# Запуск сервера
ExecStart=/home/user/temp/golang/build/gpio-monitor-server -pipe /tmp/gpio_pipe
# Перезапуск при падении
Restart=on-failure
RestartSec=10s
# Лимиты и безопасность
LimitNOFILE=4096
NoNewPrivileges=yes
# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=gpio-monitor
[Install]
WantedBy=multi-user.target

View File

@@ -1,191 +1,177 @@
import sys
#!/usr/bin/env python3
"""
Имитатор параллельной шины для gpio-interrupt (Raspberry Pi 4)
"""
import gpiod
import time
import ast
import argparse
import sys
import signal
import os
OUT_BUFFER_LEN = 32
RECONNECT_DELAY = 0.5
REPEAT_COUNT = 50 # повтор
DEFAULT_SEND_DELAY = 0.1 # по умолчанию (10 байт/с)
stop_flag = False
from gpiod.line import Direction, Value
# ================= SIGNALS =================
class ParallelBusImitator:
"""Имитатор 7-битной параллельной шины данных"""
def handle_sigint(signum, frame):
global stop_flag
stop_flag = True
# Временные константы (секунды)
DATA_SETUP_TIME = 0.000005
STROBE_WIDTH = 0.000005
HOLD_TIME = 0.000005
WR_PIN = 12 # BCM12
signal.signal(signal.SIGINT, handle_sigint)
signal.signal(signal.SIGTERM, handle_sigint)
DATA_PINS_MAPPING = [
(21, 18, "D0 (LSB)"),
(7, 27, "D1"),
(6, 22, "D2"),
(5, 23, "D3"),
(25, 17, "D4"),
(24, 20, "D5"),
(23, 21, "D6"),
(22, None, "D7 (не подключён)"),
]
def __init__(self, use_mask=True, chip_path='/dev/gpiochip0'):
self.use_mask = use_mask
self.counter = 0
self.active_pins = []
# ================= UI =================
for wpi, bcm, desc in self.DATA_PINS_MAPPING:
if bcm is not None:
self.active_pins.append((wpi, bcm, desc))
last_status = ""
self.data_pins_bcm = [bcm for _, bcm, _ in self.active_pins]
self.all_pins = self.data_pins_bcm + [self.WR_PIN]
def status(msg):
global last_status
if msg != last_status:
sys.stderr.write("\r" + msg + " " * 20)
sys.stderr.flush()
last_status = msg
def status_clear():
sys.stderr.write("\r" + " " * 80 + "\r")
sys.stderr.flush()
# ================= DATA =================
def load_array(path):
while True:
try:
with open(path, "r") as f:
content = f.read().strip()
data = ast.literal_eval(content)
result = []
for x in data:
if isinstance(x, str):
x = int(x, 0)
result.append(x & 0xFF)
print(f"[emu] Загружено {len(result)} байт", file=sys.stderr)
return result
self.chip = gpiod.Chip(chip_path)
self.lines = self.chip.request_lines(
{
p: gpiod.LineSettings(
direction=Direction.OUTPUT,
output_value=Value.INACTIVE
)
for p in self.all_pins
},
consumer="par-bus-imitator"
)
except PermissionError:
print("Нет прав доступа к GPIO. Используйте sudo.")
sys.exit(1)
except Exception as e:
print(f"[emu] Ошибка чтения: {e}", file=sys.stderr)
time.sleep(1)
print(f"Ошибка GPIO: {e}")
sys.exit(1)
bit_width = 4 if use_mask else len(self.data_pins_bcm)
# ================= PIPE =================
print(f"✓ Имитатор шины ({bit_width}-бит режим)")
print("Нажмите Ctrl+C для остановки\n")
def open_pipe(path):
while not stop_flag:
try:
fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK)
status("[emu] pipe: подключен")
return os.fdopen(fd, "wb", buffering=0)
except OSError:
status("[emu] pipe: нет читателя")
time.sleep(RECONNECT_DELAY)
return None
# ================= ARGS PARSING =================
def parse_args():
"""
Разбирает аргументы командной строки.
Использование:
python3 emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]
Где:
задержка — float, секунд между байтами (по умолчанию DEFAULT_SEND_DELAY)
logfile — путь к файлу лога или "-" для stderr
"""
if len(sys.argv) < 3:
print("Использование: python emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]", file=sys.stderr)
sys.exit(1)
array_path = sys.argv[1]
pipe_path = sys.argv[2]
send_delay = DEFAULT_SEND_DELAY
log_file = None
arg_offset = 0
if len(sys.argv) > 3:
try:
send_delay = float(sys.argv[3])
arg_offset = 1 # сдвигаемся, значит logfile будет 4-м аргументом
print(f"[emu] Задержка: {send_delay} с (~{1/send_delay:.0f} байт/с)", file=sys.stderr)
except ValueError:
arg_offset = 0
# Лог-файл
log_idx = 3 + arg_offset
if len(sys.argv) > log_idx:
log_path = sys.argv[log_idx]
if log_path == "-":
log_file = sys.stderr
def send_byte(self, value):
"""Отправка одного байта"""
if self.use_mask:
value &= 0x0F
else:
log_file = open(log_path, "a")
value &= 0x7F
return array_path, pipe_path, send_delay, log_file
out = {}
for bit_pos, bcm_pin in enumerate(self.data_pins_bcm):
bit = (value >> bit_pos) & 1
out[bcm_pin] = Value.ACTIVE if bit else Value.INACTIVE
self.lines.set_values(out)
time.sleep(self.DATA_SETUP_TIME)
self.lines.set_value(self.WR_PIN, Value.ACTIVE)
time.sleep(self.STROBE_WIDTH)
self.lines.set_value(self.WR_PIN, Value.INACTIVE)
time.sleep(self.HOLD_TIME)
self.counter += 1
self.show_status(value)
def show_status(self, value):
"""Красивый статус без спама"""
bits = 4 if self.use_mask else 7
bin_val = format(value, f'0{bits}b')
msg = (
f"\r▶ Передача: "
f"HEX=0x{value:02X} | "
f"BIN={bin_val} | "
f"Пакетов: {self.counter}"
)
print(msg, end='', flush=True)
def send_sequence(self, values, delay=0.2):
for val in values:
self.send_byte(val)
time.sleep(delay)
def cleanup(self):
if hasattr(self, 'lines'):
self.lines.release()
if hasattr(self, 'chip'):
self.chip.close()
# ================= MAIN =================
def handle_signal(signum, frame):
print("\nЗавершение работы...")
sys.exit(0)
def main():
array_path, pipe_path, send_delay, log_file = parse_args()
signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGTERM, handle_signal)
data = load_array(array_path)
parser = argparse.ArgumentParser(
description='Имитатор параллельной шины'
)
log_buffer = []
total_bytes = 0
packet_id = 0
i = 0
repeat = 0
parser.add_argument('--no-mask', action='store_true',
help='7-битный режим')
parser.add_argument('--test',
choices=['default', 'counter', 'pattern'],
default='default')
parser.add_argument('--delay', type=float, default=0.2)
parser.add_argument('--single', type=str)
parser.add_argument('--chip', default='/dev/gpiochip0')
pipe = None
args = parser.parse_args()
while not stop_flag:
imitator = ParallelBusImitator(
use_mask=not args.no_mask,
chip_path=args.chip
)
if pipe is None:
pipe = open_pipe(pipe_path)
if pipe is None:
break
try:
if args.single:
val = int(args.single, 16)
imitator.send_byte(val)
print()
return
try:
byte = data[i]
if args.test == 'default':
test_data = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40]
elif args.test == 'counter':
test_data = list(range(128))
else:
test_data = [0x00, 0x01, 0x03, 0x07, 0x0F, 0x55, 0x2A]
pipe.write(bytes([byte]))
while True:
imitator.send_sequence(test_data, args.delay)
log_buffer.append(byte)
total_bytes += 1
if len(log_buffer) >= OUT_BUFFER_LEN and log_file:
packet_id += 1
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except BrokenPipeError:
pipe.close()
pipe = None
status("[emu] pipe: отключён")
except Exception as e:
status(f"[emu] ошибка: {e}")
time.sleep(1)
status_clear()
print("[emu] завершён", file=sys.stderr)
except KeyboardInterrupt:
print("\nОстановка...")
except Exception as e:
print(f"\nОшибка: {e}")
finally:
imitator.cleanup()
print("✓ GPIO освобождены")
if __name__ == "__main__":
main()
main()