Vyacheslav Vinokurov пре 1 месец
родитељ
комит
922b2c00b3
4 измењених фајлова са 392 додато и 0 уклоњено
  1. BIN
      rp_apps/README.docx
  2. 390 0
      rp_apps/README.md
  3. BIN
      rp_apps/~$README.docx
  4. 2 0
      spectrometer_service/mserv00/AddDevices.bat

BIN
rp_apps/README.docx


+ 390 - 0
rp_apps/README.md

@@ -0,0 +1,390 @@
+# Red Pitaya: деплой, запуск и работа со спектрометром
+
+Связка из трёх частей:
+
+| Часть | Где работает | Что делает |
+|---|---|---|
+| `spec_server` ([spec_server.c](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):
+
+```sh
+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. Сборка на плате
+
+```sh
+ssh root@169.254.156.186
+cd /root
+make
+```
+
+## 4. Запуск сервера
+
+```sh
+./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-сессии:
+
+```sh
+nohup ./spec_server --bind 169.254.156.186 > spec_server.log 2>&1 &
+```
+
+## 5а. Клиент: интерактивная оболочка
+
+На ПК (Python 3, сторонних пакетов не нужно):
+
+```sh
+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 ...`.
+
+Одна команда без входа в оболочку:
+
+```sh
+python spec_client.py --host 169.254.156.186 status
+```
+
+Ключи: `--port` (5001), `--timeout` (5 с).
+
+## 5б. Клиент: прогон файла последовательности
+
+```sh
+python spec_client.py --host 169.254.156.186 --run test_sequence.spec
+```
+
+Файл исполняется построчно; пустые строки и всё после `#` игнорируются. Каждая строка печатается с именем файла, номером и ответом. По умолчанию прогон **останавливается на первой ошибке**; `-k` — продолжать до конца. Код возврата: `0` — без ошибок, `1` — были.
+
+Готовые файлы:
+
+- [test_sequence.spec](test_sequence.spec) — полный проверочный прогон: TTL, РЧ, цепочки с `waitbuf`, три сценария с аппаратным секвенсором, `seqabort`. Должен пройти без единой ошибки.
+- [test_contracts.spec](test_contracts.spec) — намеренно ошибочные команды, проверка отказов. Гонять с `-k`, ненулевой код возврата — норма.
+
+## 5в. Django-сервис с `RedPitayaEngine`
+
+### Запуск сервиса
+
+```bat
+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).
+
+```bat
+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](../spectrometer_service/mserv00/spectrometer/engine.py#L793)).
+
+| | `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](../spectrometer_service/mserv00/spectrometer/engine.py#L666-L668) (`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](../spectrometer_service/templates/template_post.json), поменять `engine` и `isdr`:
+
+```json
+{
+  "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 }
+  }
+}
+```
+
+```sh
+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 |

BIN
rp_apps/~$README.docx


+ 2 - 0
spectrometer_service/mserv00/AddDevices.bat

@@ -2,6 +2,8 @@ curl -v -u specadmin:specadmin -X POST -H "Content-Type: application/json" -d "{
 
 curl -v -u specadmin:specadmin -X POST -H "Content-Type: application/json" -d "{\"device_type\": \"SDR\", \"brend\": \"HackRF\", \"serial_model\": \"HackRF\", \"proto\": \"sdr_default\", \"proto_interface\": \"USB\"}" http://localhost:8000/api/devices/
 
+curl -v -u specadmin:specadmin -X POST -H "Content-Type: application/json" -d "{\"device_type\": \"SDR\", \"brend\": \"Red Pitaya\", \"serial_model\": \"RP\", \"proto\": \"rp_sdr\", \"proto_interface\": \"TCP\"}" http://localhost:8000/api/devices/
+
 curl -v -u specadmin:specadmin -X POST -H "Content-Type: application/json" -d "{\"device_type\": \"SYNC\", \"brend\": \"Arduino\", \"serial_model\": \"DuePP\", \"proto\": \"sync_default\", \"proto_interface\": \"USB\"}" http://localhost:8000/api/devices/
 
 curl -v -u specadmin:specadmin -X POST -H "Content-Type: application/json" -d "{\"device_type\": \"GRA\", \"brend\": \"ITMO\", \"serial_model\": \"GRU\", \"proto\": \"gra_default\", \"proto_interface\": \"UDP\"}" http://localhost:8000/api/devices/