README.md 22 KB

Red Pitaya: деплой, запуск и работа со спектрометром

Связка из трёх частей:

Часть Где работает Что делает
spec_server (spec_server.c) Red Pitaya (ARM, root) Отображает регистры ПЛИС по 0x43000000, слушает TCP-порт 5001, принимает текстовые команды построчно
spec_client.py ПК Оболочка / прогон .spec-файлов; та же копия лежит в spectrometer_service/mserv00/bin/ для Django
Django-сервис mserv00 ПК REST API измерения; RedPitayaEngine вызывает spec_client.py --run <файл> вместо HackRF

Адрес платы в инструкции — 169.254.156.186 (link-local, плата подключена кабелем напрямую в ПК; Windows сама выдаёт себе адрес из 169.254.0.0/16, DHCP не нужен). Логин root, пароль по умолчанию root.


1. Прошивка ПЛИС

На плате (по ssh, от root):

fpgautil -b system_wrapper3.bit.bin -f Full
cat /sys/class/fpga_manager/fpga0/state     # должно быть: operating

Что важно:

  • Нужен именно .bit.bin (после bootgen), а не .bit.
  • Прошивка не переживает перезагрузку платы: после каждого reboot — повторить.
  • Если spec_server уже запущен — остановить его (Ctrl+C) до перепрошивки, потом запустить снова.
  • Не открывать штатный веб-интерфейс Red Pitaya, пока работаете с сервером: его приложения грузят в ПЛИС свою прошивку.

2. Перенос исходников на плату (WinSCP)

WinSCP → New Session:

  • File protocol: SCP (или SFTP)
  • Host name: 169.254.156.186, port 22
  • User name: root, password: root

Скопировать в /root/ на плате:

  • rp_apps/spec_server.c
  • rp_apps/Makefile
  • system_wrapper3.bit.bin (если ещё не на плате)

3. Сборка на плате

ssh root@169.254.156.186
cd /root
make

4. Запуск сервера

./spec_server --bind 169.254.156.186

Ожидаемый вывод:

spec_server: /dev/mem 0x43000000 отображён, слушаю 169.254.156.186:5001

Дальше сервер печатает подключения/отключения клиентов. Останов — Ctrl+C. Обслуживается один клиент за раз (второй ждёт в очереди accept, пока первый не отключится).

Параметры (все необязательны):

Ключ По умолчанию Смысл
--bind <ip> 0.0.0.0 На каком адресе слушать
--port <N> 5001 TCP-порт
--base <адрес> 0x43000000 База регистров в AXI
--dev <файл> /dev/mem Что отображать; обычный файл на 4 КБ даёт «стенд без платы»
--force — Не проверять состояние fpga_manager (опасно)

Чтобы сервер жил после закрытия ssh-сессии:

nohup ./spec_server --bind 169.254.156.186 > spec_server.log 2>&1 &

5а. Клиент: интерактивная оболочка

На ПК (Python 3, сторонних пакетов не нужно):

cd rp_apps
python spec_client.py --host 169.254.156.186
OK spec_server, база 0x43000000, help — список команд
help — команды сервера, ? — команды клиента
spec> status
OK status=0x08 busy=0 last_valid=0 buf_valid=0 ready=1 overflow=0 seq_busy=0 seq_len=0
spec> set freq 2500000
OK inc=0x051eb852 (2500000.0 Гц)
spec> pulse 20 2500000 0x0002
...
spec> quit

help — список команд сервера, ? — команд клиента, ? set — справка по конкретной. Всё, что оболочка не знает сама, уходит на сервер как есть. Ответ сервера всегда одна строка: OK ... либо ERR ....

Одна команда без входа в оболочку:

python spec_client.py --host 169.254.156.186 status

Ключи: --port (5001), --timeout (5 с).

5б. Клиент: прогон файла последовательности

python spec_client.py --host 169.254.156.186 --run test_sequence.spec

Файл исполняется построчно; пустые строки и всё после # игнорируются. Каждая строка печатается с именем файла, номером и ответом. По умолчанию прогон останавливается на первой ошибке; -k — продолжать до конца. Код возврата: 0 — без ошибок, 1 — были.

Готовые файлы:

  • test_sequence.spec — полный проверочный прогон: TTL, РЧ, цепочки с waitbuf, три сценария с аппаратным секвенсором, seqabort. Должен пройти без единой ошибки.
  • test_contracts.spec — намеренно ошибочные команды, проверка отказов. Гонять с -k, ненулевой код возврата — норма.

5в. Django-сервис с RedPitayaEngine

Запуск сервиса

cd spectrometer_service\mserv00
autorun_default_venv.bat      :: первый раз: создаёт mvenv, ставит зависимости
autorun_default.bat           :: далее

Что делают батники: убивают старый pico-tcp.exe, активируют venv, pip install -r requirements.txt, запускают bin\pico-tcp.exe (TCP-сервер Picoscope на порту 5003) и python manage.py runserver (порт 8000).

python -m venv mvenv
mvenv\Scripts\activate.bat
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser        :: AddDevices.bat ждёт specadmin/specadmin
AddDevices.bat                          :: один раз: завести устройства в БД
start bin\pico-tcp.exe
python manage.py runserver

Важно: runserver должен быть запущен из активированного venv и из каталога mserv00 — движок вызывает python bin\spec_client.py через PATH, а пути к .spec-файлам разрешаются относительно текущего каталога процесса.

Отличия RedPitayaEngine от DefaultEngine

Движок выбирается полем "engine" в info при POST /api/measure/ (engine.py:793).

DefaultEngine RedPitayaEngine
Передатчик HackRF через bin\hackrftrans00.exe -t <file> -f <freq> -s <srate> -a <ampl> -x <gain> Red Pitaya: python bin\spec_client.py --host 169.254.156.186 --run <file>
isdr.file .bin с IQ-отсчётами путь к .spec-файлу (абсолютный или относительно mserv00)
isdr.freq, srate, ampl, gain Передаются в HackRF Игнорируются — несущая, амплитуда и т. д. задаются командами set внутри .spec
Адрес платы — Зашит в engine.py:666-668 (rp_sdr(..., ip='169.254.156.186')); другой адрес — править там
Синхронизатор (Arduino DuePP, Sync.exe) syncUp → syncWait перед каждым измерением, триггер АЦП от Arduino Не используется вовсе: ни syncUp, ни syncWait, COM-порт не открывается. Триггер АЦП должен приходить с TTL-линии Red Pitaya
Блок isync в JSON Обязателен Обязателен (сериализатор требует), но содержимое не используется — оставьте как в шаблоне
Порядок в одном измерении sync → ADC arm → 1 с → HackRF ADC arm (adc_interface.start в отдельном потоке) → 1 с → прогон .spec → ожидание АЦП → запись данных
Усреднение iadc.averaging повторов То же: .spec прогоняется averaging раз целиком, между повторами пауза averaging_delay с
Градиенты (igrax/y/z) одинаково одинаково

Пример запроса

Взять templates/template_post.json, поменять engine и isdr:

{
  "info": {
    "infostr": "fid_rp",
    "engine": "RedPitayaEngine",
    "time": null,
    "iadc": {
      "device_model": "PS4000A",
      "srate": 80000000, "points": [1000],
      "n_channels": 2, "channel_ranges": [8, 8],
      "n_triggers": 1, "averaging": 4, "averaging_delay": 1.0,
      "trigger_channel": 0, "trig_direction": 0, "threshold": 5000,
      "auto_measure_time": 10000, "enabled": true
    },
    "isync": { "device_model": "DuePP", "file": "test.xml", "port": 7 }, # плевать, но что-то должно стоять
    "isdr": {
      "device_model": "RP",
      "srate": 100000, "freq": 100000, "ampl": true, "gain": 35, # плевать, но что-то должно стоять
      "file": "C:\\Work\\lowfield_mri_programs\\rp_apps\\fid.spec"
    },
    "igrax": { "device_model": "GRU", "ip": "127.0.0.1", "file": "test.txt", "enabled": false },
    "igray": { "device_model": "GRU", "ip": "127.0.0.1", "file": "test.txt", "enabled": false },
    "igraz": { "device_model": "GRU", "ip": "127.0.0.1", "file": "test.txt", "enabled": false }
  }
}
curl -u specadmin:specadmin -H "Content-Type: application/json" -d @post.json http://localhost:8000/api/measure/
# -> {"status":"created","measurement_id":N,...}
curl -u specadmin:specadmin http://localhost:8000/api/measure/N/state/    # статус
curl -u specadmin:specadmin http://localhost:8000/api/measure/N/data/     # данные (base64 int16)

Команды спектрометра

Модель

Единица работы тракта — событие. start ставит событие в очередь; когда до него доходит очередь, на dur мкс поднимаются TTL-линии из mask, и — если это start rf — одновременно излучается РЧ-импульс: несущая freq, начальная фаза phase, форма огибающей env (таблица id 0..3, амплитуда scale), длительность pulse. События идут строго друг за другом, без зазоров. Глубина очереди — три события (буфер + слот + идущее).

Параметры задаются set-командами до start и действуют на все последующие события, пока их не поменяют. Сервер держит теневую копию параметров (params), поскольку регистры ПЛИС только на запись.

Пауза между импульсами — это тоже событие: set mask 0x0000, set dur <мкс>, start ttl.

Команды сервера

Команда Описание
status busy — событие идёт; buf_valid — команда ещё в буфере (параметры не защёлкнуты); ready — очередь примет команду; overflow — команда отвергнута (липкий!); seq_busy — секвенсор играет; seq_len — слов загружено
params Теневые параметры живого тракта
defaults Сброс: dur=1000, mask=0, freq=2.5 МГц, phase=0, step=0x400 (≈524 мкс), env 0 8191
set dur <мкс> Длительность окна TTL, целое > 0
set mask <0..0xFFFF> Какие из 16 TTL-линий подняты в окне
set freq <Гц> Несущая РЧ, 0 < f < 62.5e6 (Найквист при 125 МГц)
set inc <32 бита> То же, сырой фазовый инкремент
set phase <град> Начальная фаза несущей (любое число, берётся по модулю 360)
set pulse <мкс> Длительность огибающей; предел ≈ 536 870 мкс
set step <32 бита> То же, сырой шаг Q16.16 по таблице огибающей (0 запрещён — бесконечное излучение)
set env <id 0..3> <scale 0..16383> Форма огибающей и амплитуда. scale ≤ 8191, выше — переполнение и искажение формы
start ttl Событие только с TTL
start rf Событие с TTL и РЧ. Требование: огибающая не длиннее окна (pulse ≤ dur), иначе ERR
seqbegin Начать загрузку программы в секвенсор: все дальнейшие set/start/op/defaults пишутся в BRAM (до 16384 слов), а не в тракт
seqend Закончить загрузку, вернуться в живой режим
seqrun [N] Проиграть загруженную программу (все слова или первые N) на 125 МГц без участия сети
seqabort Немедленно прекратить выдачу слов (текущее событие доиграет)
op <код> <параметр> Сырая команда без проверок
peek <смещ.> / poke <смещ.> <знач.> Прямое чтение/запись регистра (0x00 OPCODE, 0x04 PARAM, 0x08 STATUS)
help, quit

Привязка бит маски к пинам

TTL-линии выведены на разъём E1 платы (STEMlab 125-14 Gen 2): ряд DIO*_P и ряд DIO*_N. Бит в set mask → пин:

Бит Пин E1 Бит Пин E1
0x0002 DIO0_P 0x0200 DIO0_N
0x0004 DIO1_P (?) 0x0400 DIO1_N
0x0004 DIO2_P (?) 0x0800 DIO2_N
0x0008 DIO3_P 0x1000 DIO3_N
0x0010 DIO4_P 0x2000 DIO4_N
0x0020 DIO5_P 0x4000 DIO5_N
0x0040 DIO6_P 0x8000 DIO6_N
0x0080 DIO7_P ? DIO7_N (не работает, причина не выяснена)

Что пока не проверено:

  • DIO1_P и DIO2_P — по текущим наблюдениям оба отзываются на 0x0004; какой бит на самом деле у каждого — уточнить.
  • DIO7_N — не удалось получить сигнал ни на одном бите.
  • Биты 0x0001 и 0x0100 в таблице не заняты.

Примеры: set mask 0x0002 — только DIO0_P; set mask 0x0082 — DIO0_P + DIO7_P; set mask 0xFFFF — все линии.

Директивы клиента (работают в оболочке и в .spec)

Директива Описание
echo <текст> Напечатать (заголовок раздела в файле)
wait <мс> Пауза на ПК
waitidle [мс] Ждать busy=0 — все события доиграли (по умолчанию 5000 мс)
waitbuf [мс] Ждать buf_valid=0 — команда ушла из буфера в тракт, параметры защёлкнуты (1000 мс)
waitseq [мс] Ждать seq_busy=0 — секвенсор выдал все слова (1000 мс). Не значит, что последнее событие доиграло: после waitseq нужен ещё waitidle
run [-k] <файл> Выполнить файл вживую (вложенность до 8)
load <файл> Тот же файл, но обернуть в seqbegin … seqend — загрузить в секвенсор, не исполняя. Внутри такого файла wait* бессмысленны
pulse <мкс> [Гц] [маска] Составная: set dur (×1.2), set pulse, set freq, set mask, start rf
connect [хост [порт]] Переподключиться

Живой режим против секвенсора

Живой режим — каждая строка идёт по сети. mask и dur защёлкиваются только в момент, когда start доходит до ttl_controller. Если поменять их, пока предыдущий start лежит в буфере (buf_valid=1), уже поставленное событие уедет с новой маской. Поэтому между звеньями цепочки в живом режиме — waitbuf, а не wait:

set dur 20
set mask 0xFFFF
set pulse 10
start rf
waitbuf 1000        # дождаться, пока старт защёлкнул mask/dur
set dur 50
set mask 0x0000
start ttl
waitbuf 1000

Секвенсор — вся программа сначала кладётся в BRAM (seqbegin … seqend), потом seqrun играет её на 125 МГц. Между словами нет сетевого окна, гонка невозможна, waitbuf внутри не нужен. Тайминги детерминированы — для реальных последовательностей использовать именно этот режим. Все контракты проверяются при загрузке так же, как вживую (ERR на start rf с длинной огибающей вылетит на этапе load).

seq_len в status знает только сервер: после его перезапуска счётчик обнуляется, хотя BRAM на плате не тронута — тогда seqrun N с явным числом слов.

Пример:

seqbegin
defaults
set freq 2500000          # несущая 2.5 МГц
set env 0 8191            # огибающая №0, максимальная амплитуда

# 90°: 20 мкс РЧ в окне 30 мкс, ключ ТХ поднят
set dur 30
set mask 0x0002
set phase 0
set pulse 20
start rf

# τ = 500 мкс тишины (событие без линий)
set dur 500
set mask 0x0000
start ttl

# 180°: 40 мкс, фаза 90°
set dur 50
set mask 0x0002
set phase 90
set pulse 40
start rf

# пауза до эха, затем строб АЦП (линия 0) на время приёма
set dur 450
set mask 0x0000
start ttl

set dur 1000
set mask 0x0001
start ttl
seqend

status                    # seq_len=22
seqrun
waitseq 1000              # секвенсор выдал все слова
waitidle 5000             # последнее событие (окно приёма) доиграло
status                    # busy=0 overflow=0 — успех

defaults

Тот же файл можно разнести: тело программы (без seqbegin/seqend/seqrun/wait*) — в se_prog.spec, а обёртка:

# se_run.spec
load se_prog.spec
seqrun
waitseq 1000
waitidle 5000
status

Пример: одиночный импульс вживую (FID)

# fid.spec — строб АЦП, затем один РЧ-импульс. Живой режим.
defaults
set freq 2500000
set env 0 8191

set dur 10
set mask 0x0001           # строб на триггерный вход АЦП
start ttl
waitbuf 1000

set dur 30
set mask 0x0002
set pulse 20
start rf
waitidle 5000
status

Типичные ERR и что они значат

Ответ Причина
огибающая … длиннее окна TTL … (контракт 2) pulse > dur перед start rf — увеличить dur или укоротить pulse
set step: … нуль = бесконечное излучение (контракт 1) step 0 / слишком длинный pulse
set freq: нужно 0 < f < 62500000 Гц Выше Найквиста
set env: id 0..3, scale 0..16383 Вне диапазона
очередь занята (ready=0, buf_valid=1) Три события уже в очереди — добавить waitbuf/waitidle
команда отвергнута, поднят overflow Очередь переполнилась на стороне ПЛИС; флаг липкий — перезагрузить битстрим (п. 1)
последовательность заполнена (16384 слов) Программа не влезает в BRAM
seqrun: нечего играть (0 слов) Не было seqbegin … seqend (или сервер перезапущен — задать seqrun N)
fpga_manager в состоянии '…', а не 'operating' (при старте сервера) Битстрим не загружен — п. 1