AD7766 на Tang Primer 25K: отладочная прошивка, структура после рефакторинга
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# Gowin IDE build output, кроме настроек синтеза/PnR
|
||||||
|
impl/*
|
||||||
|
!impl/*_process_config.json
|
||||||
|
*.gprj.user
|
||||||
|
|
||||||
|
# Даташит: большой, не трекаем
|
||||||
|
docs/ad7766.pdf
|
||||||
|
docs/ad7766_datasheet.md
|
||||||
|
|
||||||
|
# Захваченные данные
|
||||||
|
*.bin
|
||||||
|
*.raw
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE gowin-fpga-project>
|
||||||
|
<Project>
|
||||||
|
<Template>FPGA</Template>
|
||||||
|
<Version>5</Version>
|
||||||
|
<Device name="" pn="">gw5a25b-003</Device>
|
||||||
|
<FileList>
|
||||||
|
<File path="src/UART.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/led_status.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/SPI_Master.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/SPI_Ctrl.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/decimator.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/fifo_top.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/Packets_creator.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/Top.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/AD7766.cst" type="file.cst" enable="1"/>
|
||||||
|
<File path="src/AD7766.sdc" type="file.sdc" enable="1"/>
|
||||||
|
</FileList>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# AD7766 на Tang Primer 25K
|
||||||
|
|
||||||
|
Отладочная прошивка для AD7766: непрерывная оцифровка дифференциального входа
|
||||||
|
и выдача отсчётов по UART для просмотра в `F:\PyPrj\Signal_analayzer`.
|
||||||
|
|
||||||
|
MCLK 1.000 МГц -> ODR 125 kSPS -> дециматор sinc^1 /10 -> **12 500 Гц** ->
|
||||||
|
кадры по 50 отсчётов -> UART 921600.
|
||||||
|
|
||||||
|
Параметры и обоснования со ссылками на даташит — [docs/design.md](docs/design.md).
|
||||||
|
Распиновка — [docs/pinout.md](docs/pinout.md).
|
||||||
|
|
||||||
|
## Состав
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|:--|:--|
|
||||||
|
| `src/Top.vhd` | верхний уровень: только экземпляры и связи, без логики |
|
||||||
|
| `src/SPI_Master.vhd` | битовый уровень обмена: CS, SCLK, защёлка SDO |
|
||||||
|
| `src/SPI_Ctrl.vhd` | MCLK, сброс через SYNC/PD, запуск чтения по спаду DRDY |
|
||||||
|
| `src/decimator.vhd` | усреднение по 10 отсчётам |
|
||||||
|
| `src/fifo_top.vhd` | буфер отсчётов, заглушка под IP-ядро Gowin |
|
||||||
|
| `src/Packets_creator.vhd` | сборка кадра с CRC-16 |
|
||||||
|
| `src/UART.vhd` | передатчик, взят из `zvp_da_common` без изменений |
|
||||||
|
| `src/led_status.vhd` | индикация частотой мигания |
|
||||||
|
| `src/AD7766.cst` | привязка выводов |
|
||||||
|
| `tb/ad7766_model.vhd` | поведенческая модель АЦП по Table 3 |
|
||||||
|
| `tb/tb_top.vhd` | сквозной тест: модель -> ПЛИС -> UART -> разбор кадра |
|
||||||
|
| `tools/uart_capture.py` | приём кадров, запись `.raw` и `.bin` |
|
||||||
|
| `tools/uart_live.py` | живой просмотр: осциллограмма, спектр, счётчики |
|
||||||
|
|
||||||
|
## Симуляция (GHDL)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ghdl -a --std=08 --workdir=build src/fifo_top.vhd src/UART.vhd src/led_status.vhd src/SPI_Master.vhd src/SPI_Ctrl.vhd src/decimator.vhd src/Packets_creator.vhd src/Top.vhd tb/ad7766_model.vhd tb/tb_top.vhd && ghdl -e --std=08 --workdir=build tb_top && ./tb_top --stop-time=20ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Модель выдаёт группами по 10 одинаковых слов, поэтому усреднённый отсчёт обязан
|
||||||
|
совпасть со значением группы побитово. Число проверяемых кадров задаётся
|
||||||
|
`-gFRAMES=N` (один кадр ~4 мс модельного времени).
|
||||||
|
|
||||||
|
## Запись с железа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
py -3 tools/uart_capture.py --list
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
py -3 tools/uart_capture.py --port COM5 --seconds 10 --out test1
|
||||||
|
```
|
||||||
|
|
||||||
|
Получаются `test1.raw` (весь поток как пришёл, для отладки канала),
|
||||||
|
`test1.bin` (только отсчёты, читается `read_24bit_bin()`) и `test1.log`.
|
||||||
|
|
||||||
|
В `Signal_analayzer` для этих данных: `'FS': 12500`, `'V_REF': 5`.
|
||||||
|
|
||||||
|
## Живой просмотр
|
||||||
|
|
||||||
|
```bash
|
||||||
|
py -3 tools/uart_live.py --port COM10
|
||||||
|
```
|
||||||
|
|
||||||
|
Осциллограмма, спектр и счётчики кадров в реальном времени — чтобы посмотреть
|
||||||
|
сигнал до того, как писать запись. Управление кнопками внизу окна: запись,
|
||||||
|
автомасштаб, вычитание постоянной составляющей, вид спектра, пауза. Те же
|
||||||
|
действия продублированы клавишами `r`, `a`, `d`, `s`, `f`, выход — `q`.
|
||||||
|
|
||||||
|
Запись встроена в просмотр по необходимости: COM-порт держит один процесс,
|
||||||
|
и запустить `uart_capture.py` на том же порту параллельно нельзя. Для записи
|
||||||
|
без просмотра остаётся `uart_capture.py` — он же нужен, когда важно
|
||||||
|
фиксированное время записи.
|
||||||
|
|
||||||
|
Разбор кадров у обоих инструментов общий (`FrameParser` в `uart_capture.py`):
|
||||||
|
формат описан в одном месте, так что просмотр и запись не могут разойтись.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# AD7766 — принятые решения
|
||||||
|
|
||||||
|
Ссылки на страницы — по AD7766 Data Sheet Rev. D.
|
||||||
|
|
||||||
|
## Тактирование и частота дискретизации
|
||||||
|
|
||||||
|
| Параметр | Значение | Обоснование |
|
||||||
|
|:--|:--|:--|
|
||||||
|
| Системный клок | 50 МГц (E2) | генератор Tang Primer 25K |
|
||||||
|
| MCLK | 1.000 МГц = 50/50 | t2 min 100 нс, t3 max 900 нс, f max 1.024 МГц (Table 3, стр. 6-7) |
|
||||||
|
| ODR АЦП | 125 000 Гц | ODR = MCLK/8 для AD7766 (Table 6, стр. 15) |
|
||||||
|
| Дециматор | sinc^1 (среднее по 10) | понижение обязательно: 125 kSPS x 3 байта = 375 кБ/с не влезает в UART |
|
||||||
|
| Выходная ЧД | 12 500 Гц | совпадает с FS в F:\PyPrj\Signal_analayzer |
|
||||||
|
| SCLK | 6.25 МГц = 50/8 | 24 бита за 3.84 мкс при окне чтения 7.7 мкс |
|
||||||
|
|
||||||
|
Окно чтения: DRDY низкий в течение tDRDY - t5 = 8000 - 294 = 7706 нс (Table 3).
|
||||||
|
|
||||||
|
MCLK взят 1.000 МГц вместо максимальных 1.024 МГц ради целого деления от 50 МГц
|
||||||
|
(без PLL) и ради ровной выходной ЧД 12 500 Гц.
|
||||||
|
|
||||||
|
## Интерфейс АЦП
|
||||||
|
|
||||||
|
Регистров конфигурации у AD7766 нет — интерфейс только на чтение (стр. 17).
|
||||||
|
|
||||||
|
- Сброс/синхронизация — низкий импульс на SYNC/PD (пин 7), цепь `RESET` на гребёнке.
|
||||||
|
События защёлкиваются по нарастающим фронтам MCLK (стр. 17).
|
||||||
|
- После снятия SYNC/PD выждать tSETTLING = 590 x tMCLK = 590 мкс (Table 7).
|
||||||
|
- Чтение: по спаду DRDY опустить CS, MSB появляется на SDO по спаду CS,
|
||||||
|
остальные 23 бита выдвигаются спадами SCLK (стр. 17, Figure 3).
|
||||||
|
Мастер защёлкивает биты по нарастающим фронтам SCLK.
|
||||||
|
- SDI (пин 15) — вход daisy-chain, для одиночного АЦП постоянный '0' (стр. 18).
|
||||||
|
|
||||||
|
## Шкала
|
||||||
|
|
||||||
|
VREF+ = 5 В -> синфазный уровень 2.5 В, каждый вход 0...5 В, дифференциально +-5 В
|
||||||
|
(стр. 16, 20). Полная шкала кода 0x7FFFFF = +5 В, 0x800000 = -5 В.
|
||||||
|
В Signal_analayzer нужно `'V_REF': 5`.
|
||||||
|
|
||||||
|
## Формат кадра UART
|
||||||
|
|
||||||
|
Бод 921600, 8N1. 50 отсчётов в кадре -> 157 байт, 250 кадров/с, 39.25 кБ/с (43% канала).
|
||||||
|
|
||||||
|
| Поле | Байт | Описание |
|
||||||
|
|:--|--:|:--|
|
||||||
|
| SYNC | 2 | `A5 5A` |
|
||||||
|
| FRAME_CNT | 2 | uint16 BE, инкремент на кадр |
|
||||||
|
| STATUS | 1 | бит0 — переполнение FIFO, бит1 — сорван счёт DRDY |
|
||||||
|
| DATA | 150 | 50 отсчётов x 24 бита, MSB first |
|
||||||
|
| CRC16 | 2 | CCITT, poly 0x1021, init 0xFFFF, по FRAME_CNT..DATA |
|
||||||
|
|
||||||
|
## Приём на ПК
|
||||||
|
|
||||||
|
Пишутся два файла:
|
||||||
|
|
||||||
|
- `*.raw` — байт в байт всё, что пришло из порта, до разбора. Отладочный.
|
||||||
|
- `*.bin` — только отсчёты, 3 байта BE на отсчёт, без служебных полей.
|
||||||
|
Читается функцией `read_24bit_bin()` из Signal_analayzer без переделок.
|
||||||
|
|
||||||
|
Кадр с несошедшейся CRC отбрасывается, а пропуск по FRAME_CNT добивается нулями,
|
||||||
|
чтобы не сдвинулась ось времени. Места вставок пишутся в лог.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Пинаут: плата AD7766 <-> Tang Primer 25K
|
||||||
|
|
||||||
|
## Гребёнка платы АЦП
|
||||||
|
|
||||||
|
| № на гребёнке | Название цепи | Вывод AD7766 (TSSOP-16) | Направление (со стороны ПЛИС) | Пин ПЛИС |
|
||||||
|
|:--:|:--|:--|:--|:--:|
|
||||||
|
| 1 | SDO | 10 — SDO | in | G2 |
|
||||||
|
| 2 | DRDY | 12 — DRDY | in | L4 |
|
||||||
|
| 3 | SCLK | 13 — SCLK | out | L1 |
|
||||||
|
| 4 | CLK_ADC_IN | 14 — MCLK | out | L2 |
|
||||||
|
| 5 | SDI | 15 — SDI | out | K4 |
|
||||||
|
| 6 | CS | 16 — CS | out | J4 |
|
||||||
|
| 7 | RESET | 7 — SYNC/PD | out | G1 |
|
||||||
|
|
||||||
|
## Служебные цепи отладочной платы
|
||||||
|
|
||||||
|
| Цепь | Назначение | Пин ПЛИС |
|
||||||
|
|:--|:--|:--:|
|
||||||
|
| clk_50m | тактовый генератор платы, 50 МГц | E2 |
|
||||||
|
| uart_tx | UART на внешний CP2102, ПЛИС -> ПК | **G5** |
|
||||||
|
| led0 | светодиод | E8 |
|
||||||
|
| key | кнопка, не используется | H11 |
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"BACKGROUND_PROGRAMMING" : "off",
|
||||||
|
"COMPRESS" : false,
|
||||||
|
"CPU" : true,
|
||||||
|
"CRC_CHECK" : true,
|
||||||
|
"Clock_Route_Order" : 0,
|
||||||
|
"Convert_SDP32_36_to_SDP16_18" : true,
|
||||||
|
"Correct_Hold_Violation" : true,
|
||||||
|
"DONE" : false,
|
||||||
|
"DOWNLOAD_SPEED" : "default",
|
||||||
|
"Disable_Insert_Pad" : false,
|
||||||
|
"ENABLE_CTP" : false,
|
||||||
|
"ENABLE_MERGE_MODE" : false,
|
||||||
|
"ENCRYPTION_KEY" : false,
|
||||||
|
"ENCRYPTION_KEY_TEXT" : "00000000000000000000000000000000",
|
||||||
|
"ERROR_DECTION_AND_CORRECTION" : false,
|
||||||
|
"ERROR_DECTION_ONLY" : false,
|
||||||
|
"ERROR_INJECTION" : false,
|
||||||
|
"EXTERNAL_MASTER_CONFIG_CLOCK" : false,
|
||||||
|
"Enable_DSRM" : false,
|
||||||
|
"FORMAT" : "binary",
|
||||||
|
"FREQUENCY_DIVIDER" : "1",
|
||||||
|
"Generate_Constraint_File_of_Ports" : false,
|
||||||
|
"Generate_IBIS_File" : false,
|
||||||
|
"Generate_Plain_Text_Timing_Report" : false,
|
||||||
|
"Generate_Post_PNR_Simulation_Model_File" : false,
|
||||||
|
"Generate_Post_Place_File" : false,
|
||||||
|
"Generate_SDF_File" : false,
|
||||||
|
"Generate_VHDL_Post_PNR_Simulation_Model_File" : false,
|
||||||
|
"Global_Freq" : "default",
|
||||||
|
"GwSyn_Loop_Limit" : 2000,
|
||||||
|
"HOTBOOT" : false,
|
||||||
|
"I2C" : false,
|
||||||
|
"I2C_SLAVE_ADDR" : "00",
|
||||||
|
"INCREMENTAL_PLACE_AND_ROUTING" : "0",
|
||||||
|
"INCREMENTAL_PLACE_ONLY" : "0",
|
||||||
|
"IncludePath" : [
|
||||||
|
|
||||||
|
],
|
||||||
|
"Incremental_Compile" : "",
|
||||||
|
"Initialize_Primitives" : false,
|
||||||
|
"JTAG" : false,
|
||||||
|
"MODE_IO" : false,
|
||||||
|
"MSPI" : false,
|
||||||
|
"MSPI_JUMP" : false,
|
||||||
|
"MULTIBOOT_ADDRESS_WIDTH" : "24",
|
||||||
|
"MULTIBOOT_MODE" : "Single",
|
||||||
|
"MULTIBOOT_SPI_FLASH_ADDRESS" : "000000",
|
||||||
|
"MULTIJUMP_ADDRESS_WIDTH" : "24",
|
||||||
|
"MULTIJUMP_MODE" : "Single",
|
||||||
|
"MULTIJUMP_SPI_FLASH_ADDRESS" : "000000",
|
||||||
|
"Multi_Boot" : false,
|
||||||
|
"OUTPUT_BASE_NAME" : "AD7766",
|
||||||
|
"POWER_ON_RESET_MONITOR" : true,
|
||||||
|
"PRINT_BSRAM_VALUE" : true,
|
||||||
|
"PROGRAM_DONE_BYPASS" : false,
|
||||||
|
"PlaceInRegToIob" : true,
|
||||||
|
"PlaceIoRegToIob" : true,
|
||||||
|
"PlaceOutRegToIob" : true,
|
||||||
|
"Place_Option" : "0",
|
||||||
|
"Process_Configuration_Verion" : "1.0",
|
||||||
|
"Promote_Physical_Constraint_Warning_to_Error" : true,
|
||||||
|
"READY" : true,
|
||||||
|
"RECONFIG_N" : false,
|
||||||
|
"Ram_RW_Check" : false,
|
||||||
|
"Replicate_Resources" : false,
|
||||||
|
"Report_Auto-Placed_Io_Information" : false,
|
||||||
|
"Route_Maxfan" : 23,
|
||||||
|
"Route_Option" : "0",
|
||||||
|
"Run_Timing_Driven" : true,
|
||||||
|
"SECURE_MODE" : false,
|
||||||
|
"SECURITY_BIT" : true,
|
||||||
|
"SEU_HANDLER" : false,
|
||||||
|
"SEU_HANDLER_CHECKSUM" : false,
|
||||||
|
"SEU_HANDLER_MODE" : "auto",
|
||||||
|
"SSPI" : true,
|
||||||
|
"STOP_SEU_HANDLER" : false,
|
||||||
|
"SerDes_retiming" : false,
|
||||||
|
"Show_All_Warnings" : false,
|
||||||
|
"Synthesize_tool" : "GowinSyn",
|
||||||
|
"TclPre" : "",
|
||||||
|
"TopModule" : "",
|
||||||
|
"USERCODE" : "default",
|
||||||
|
"Unused_Pin" : "As_input_tri_stated_with_pull_up",
|
||||||
|
"VCC" : "0.9",
|
||||||
|
"VCCAUX" : "3.3",
|
||||||
|
"VCCX" : "3.3",
|
||||||
|
"VHDL_Standard" : "VHDL_Std_1993",
|
||||||
|
"Verilog_Standard" : "Vlg_Std_2001",
|
||||||
|
"WAKE_UP" : "0",
|
||||||
|
"show_all_warnings" : false,
|
||||||
|
"turn_off_bg" : false
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
|
||||||
|
//All rights reserved.
|
||||||
|
//File Title: Physical Constraints file
|
||||||
|
//Tool Version: V1.9.12 (64-bit)
|
||||||
|
//Part Number: GW5A-LV25MG121NC1/I0
|
||||||
|
//Device: GW5A-25
|
||||||
|
//Device Version: B
|
||||||
|
//Created Time: Sat 08 08 12:42:06 2026
|
||||||
|
|
||||||
|
IO_LOC "LED" E8;
|
||||||
|
IO_PORT "LED" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "UART_TX" G5;
|
||||||
|
IO_PORT "UART_TX" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_SYNC_PD" G1;
|
||||||
|
IO_PORT "ADC_SYNC_PD" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_CS_N" J4;
|
||||||
|
IO_PORT "ADC_CS_N" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_SDI" K4;
|
||||||
|
IO_PORT "ADC_SDI" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_MCLK" L2;
|
||||||
|
IO_PORT "ADC_MCLK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_SCLK" L1;
|
||||||
|
IO_PORT "ADC_SCLK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_DRDY" L4;
|
||||||
|
IO_PORT "ADC_DRDY" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "ADC_SDO" G2;
|
||||||
|
IO_PORT "ADC_SDO" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "clk" E2;
|
||||||
|
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Тактовый генератор платы: 50 МГц -> период 20 нс.
|
||||||
|
// Без этого статический анализ не проверяет ни одного пути и выдаёт TA1132.
|
||||||
|
create_clock -name clk -period 20 -waveform {0 10} [get_ports {clk}]
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: Packets_creator - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
-- Кадр (157 байт при 50 отсчётах):
|
||||||
|
-- SYNC 2 байта A5 5A
|
||||||
|
-- FRAME_CNT 2 байта uint16 BE
|
||||||
|
-- STATUS 1 байт бит0 переполнение FIFO, бит1 срыв чтения DRDY
|
||||||
|
-- DATA 150 байт 50 отсчётов x 24 бита, MSB first
|
||||||
|
-- CRC16 2 байта CCITT, poly 0x1021, init 0xFFFF, по FRAME_CNT..DATA
|
||||||
|
--
|
||||||
|
-- Байты SYNC в CRC не входят: они нужны приёмнику для ресинхронизации
|
||||||
|
-- до того, как он начнёт что-либо считать.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity Packets_creator is
|
||||||
|
generic (
|
||||||
|
SAMPLES_PER_FRAME : integer := 50
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
rst_n : in std_logic;
|
||||||
|
|
||||||
|
-- Со стороны FIFO
|
||||||
|
data_ADC : in std_logic_vector(23 downto 0); -- голова очереди
|
||||||
|
dready : in std_logic; -- набрался полный кадр
|
||||||
|
rd_en : out std_logic;
|
||||||
|
|
||||||
|
-- Для флагов состояния
|
||||||
|
wr_en : in std_logic; -- запись в FIFO, вместе с fifo_full даёт переполнение
|
||||||
|
fifo_full : in std_logic;
|
||||||
|
err_overrun : in std_logic;
|
||||||
|
|
||||||
|
-- Со стороны UART
|
||||||
|
busy_uart : in std_logic;
|
||||||
|
ena_uart : out std_logic;
|
||||||
|
dout : out std_logic_vector(7 downto 0);
|
||||||
|
|
||||||
|
fault : out std_logic -- была хотя бы одна ошибка с момента сброса
|
||||||
|
);
|
||||||
|
end entity Packets_creator;
|
||||||
|
|
||||||
|
architecture Behavioral of Packets_creator is
|
||||||
|
|
||||||
|
constant SYNC0 : std_logic_vector(7 downto 0) := x"A5";
|
||||||
|
constant SYNC1 : std_logic_vector(7 downto 0) := x"5A";
|
||||||
|
|
||||||
|
type fstate_t is (F_WAIT, F_SYNC0, F_SYNC1, F_CNT_H, F_CNT_L, F_STATUS,
|
||||||
|
F_FETCH, F_FETCH1, F_FETCH2, F_DATA, F_CRC_H, F_CRC_L);
|
||||||
|
signal fstate : fstate_t := F_WAIT;
|
||||||
|
|
||||||
|
signal waiting : std_logic := '0';
|
||||||
|
signal byte_go : std_logic := '0';
|
||||||
|
signal byte_val : std_logic_vector(7 downto 0) := (others => '0');
|
||||||
|
signal byte_ack : std_logic := '0';
|
||||||
|
signal sample_reg : std_logic_vector(23 downto 0) := (others => '0');
|
||||||
|
signal sample_no : integer range 0 to SAMPLES_PER_FRAME - 1 := 0;
|
||||||
|
signal byte_idx : integer range 0 to 2 := 0;
|
||||||
|
|
||||||
|
signal frame_cnt : unsigned(15 downto 0) := (others => '0');
|
||||||
|
signal status_lat : std_logic_vector(7 downto 0) := (others => '0');
|
||||||
|
signal crc_reg : std_logic_vector(15 downto 0) := (others => '1');
|
||||||
|
|
||||||
|
signal ovf_sticky : std_logic := '0';
|
||||||
|
signal err_sticky : std_logic := '0';
|
||||||
|
signal fault_r : std_logic := '0';
|
||||||
|
|
||||||
|
type tstate_t is (T_IDLE, T_SEND, T_WAIT);
|
||||||
|
signal tstate : tstate_t := T_IDLE;
|
||||||
|
signal hold : std_logic_vector(7 downto 0) := (others => '0');
|
||||||
|
|
||||||
|
-- CRC-16/CCITT по одному байту, MSB-first
|
||||||
|
function crc16_upd(crc : std_logic_vector(15 downto 0);
|
||||||
|
d : std_logic_vector(7 downto 0))
|
||||||
|
return std_logic_vector is
|
||||||
|
variable c : std_logic_vector(15 downto 0) := crc;
|
||||||
|
variable fb : std_logic;
|
||||||
|
begin
|
||||||
|
for i in 7 downto 0 loop
|
||||||
|
fb := c(15) xor d(i);
|
||||||
|
c := c(14 downto 0) & '0';
|
||||||
|
if fb = '1' then
|
||||||
|
c := c xor x"1021";
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
return c;
|
||||||
|
end function;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
fault <= fault_r;
|
||||||
|
|
||||||
|
-- Отдача байта в UART: ждём освобождения передатчика, даём однотактовый ena,
|
||||||
|
-- подтверждаем вызывающему по факту взведения busy_uart
|
||||||
|
tx_proc : process (clk, rst_n)
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
tstate <= T_IDLE;
|
||||||
|
hold <= (others => '0');
|
||||||
|
ena_uart <= '0';
|
||||||
|
dout <= (others => '0');
|
||||||
|
byte_ack <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
ena_uart <= '0';
|
||||||
|
byte_ack <= '0';
|
||||||
|
|
||||||
|
case tstate is
|
||||||
|
when T_IDLE =>
|
||||||
|
if byte_go = '1' then
|
||||||
|
hold <= byte_val;
|
||||||
|
tstate <= T_SEND;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when T_SEND =>
|
||||||
|
if busy_uart = '0' then
|
||||||
|
dout <= hold;
|
||||||
|
ena_uart <= '1';
|
||||||
|
tstate <= T_WAIT;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when T_WAIT =>
|
||||||
|
if busy_uart = '1' then
|
||||||
|
byte_ack <= '1';
|
||||||
|
tstate <= T_IDLE;
|
||||||
|
end if;
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
end process tx_proc;
|
||||||
|
|
||||||
|
frame_proc : process (clk, rst_n)
|
||||||
|
variable b : std_logic_vector(7 downto 0);
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
fstate <= F_WAIT;
|
||||||
|
waiting <= '0';
|
||||||
|
byte_go <= '0';
|
||||||
|
byte_val <= (others => '0');
|
||||||
|
rd_en <= '0';
|
||||||
|
sample_reg <= (others => '0');
|
||||||
|
sample_no <= 0;
|
||||||
|
byte_idx <= 0;
|
||||||
|
frame_cnt <= (others => '0');
|
||||||
|
status_lat <= (others => '0');
|
||||||
|
crc_reg <= (others => '1');
|
||||||
|
ovf_sticky <= '0';
|
||||||
|
err_sticky <= '0';
|
||||||
|
fault_r <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
byte_go <= '0';
|
||||||
|
rd_en <= '0';
|
||||||
|
|
||||||
|
if waiting = '1' then
|
||||||
|
if byte_ack = '1' then
|
||||||
|
waiting <= '0';
|
||||||
|
case fstate is
|
||||||
|
when F_SYNC0 => fstate <= F_SYNC1;
|
||||||
|
when F_SYNC1 => fstate <= F_CNT_H;
|
||||||
|
when F_CNT_H => fstate <= F_CNT_L;
|
||||||
|
when F_CNT_L => fstate <= F_STATUS;
|
||||||
|
when F_STATUS =>
|
||||||
|
sample_no <= 0;
|
||||||
|
fstate <= F_FETCH;
|
||||||
|
when F_DATA =>
|
||||||
|
if byte_idx = 2 then
|
||||||
|
byte_idx <= 0;
|
||||||
|
if sample_no = SAMPLES_PER_FRAME - 1 then
|
||||||
|
fstate <= F_CRC_H;
|
||||||
|
else
|
||||||
|
sample_no <= sample_no + 1;
|
||||||
|
fstate <= F_FETCH;
|
||||||
|
end if;
|
||||||
|
else
|
||||||
|
byte_idx <= byte_idx + 1;
|
||||||
|
end if;
|
||||||
|
when F_CRC_H => fstate <= F_CRC_L;
|
||||||
|
when F_CRC_L =>
|
||||||
|
frame_cnt <= frame_cnt + 1;
|
||||||
|
fstate <= F_WAIT;
|
||||||
|
when others => fstate <= F_WAIT;
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
else
|
||||||
|
case fstate is
|
||||||
|
|
||||||
|
when F_WAIT =>
|
||||||
|
-- Кадр начинаем только когда отсчётов набралось на полный кадр:
|
||||||
|
-- иначе передатчик упрётся в пустой FIFO посреди кадра
|
||||||
|
if dready = '1' then
|
||||||
|
crc_reg <= (others => '1');
|
||||||
|
status_lat <= (1 => err_sticky, 0 => ovf_sticky, others => '0');
|
||||||
|
ovf_sticky <= '0';
|
||||||
|
err_sticky <= '0';
|
||||||
|
fstate <= F_SYNC0;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when F_SYNC0 =>
|
||||||
|
byte_val <= SYNC0;
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_SYNC1 =>
|
||||||
|
byte_val <= SYNC1;
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_CNT_H =>
|
||||||
|
b := std_logic_vector(frame_cnt(15 downto 8));
|
||||||
|
byte_val <= b;
|
||||||
|
crc_reg <= crc16_upd(crc_reg, b);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_CNT_L =>
|
||||||
|
b := std_logic_vector(frame_cnt(7 downto 0));
|
||||||
|
byte_val <= b;
|
||||||
|
crc_reg <= crc16_upd(crc_reg, b);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_STATUS =>
|
||||||
|
byte_val <= status_lat;
|
||||||
|
crc_reg <= crc16_upd(crc_reg, status_lat);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
-- Импульс rd_en, и очередное слово
|
||||||
|
-- появляется на data_ADC со следующего такта. Два такта ожидания
|
||||||
|
-- вместо одного -- запас, обмен всё равно идёт раз в три байта UART.
|
||||||
|
when F_FETCH =>
|
||||||
|
rd_en <= '1';
|
||||||
|
fstate <= F_FETCH1;
|
||||||
|
|
||||||
|
when F_FETCH1 =>
|
||||||
|
fstate <= F_FETCH2;
|
||||||
|
|
||||||
|
when F_FETCH2 =>
|
||||||
|
sample_reg <= data_ADC;
|
||||||
|
byte_idx <= 0;
|
||||||
|
fstate <= F_DATA;
|
||||||
|
|
||||||
|
when F_DATA =>
|
||||||
|
case byte_idx is
|
||||||
|
when 0 => b := sample_reg(23 downto 16);
|
||||||
|
when 1 => b := sample_reg(15 downto 8);
|
||||||
|
when others => b := sample_reg(7 downto 0);
|
||||||
|
end case;
|
||||||
|
byte_val <= b;
|
||||||
|
crc_reg <= crc16_upd(crc_reg, b);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_CRC_H =>
|
||||||
|
byte_val <= crc_reg(15 downto 8);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
when F_CRC_L =>
|
||||||
|
byte_val <= crc_reg(7 downto 0);
|
||||||
|
byte_go <= '1';
|
||||||
|
waiting <= '1';
|
||||||
|
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Флаги ошибок взводятся после автомата, чтобы взвод не потерялся
|
||||||
|
-- на такте очистки в начале кадра
|
||||||
|
if wr_en = '1' and fifo_full = '1' then
|
||||||
|
ovf_sticky <= '1';
|
||||||
|
fault_r <= '1';
|
||||||
|
end if;
|
||||||
|
if err_overrun = '1' then
|
||||||
|
err_sticky <= '1';
|
||||||
|
fault_r <= '1';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
end process frame_proc;
|
||||||
|
|
||||||
|
end architecture Behavioral;
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: spi_ctrl - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
-- Управление AD7766 поверх spi_master.
|
||||||
|
-- Обмен с мастером через spi_ena и spi_busy.
|
||||||
|
-- Сброс фильтра импульсом SYNC/PD и выдержка tSETTLING.
|
||||||
|
-- Тактирование АЦП: частота дискретизации задаётся входом MCLK и MCLK_HALF.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity spi_ctrl is
|
||||||
|
generic (
|
||||||
|
-- MCLK = clk / (2*MCLK_HALF). Ограничения: min 100 нс, max 900 нс,
|
||||||
|
-- При clk 50 МГц и MCLK_HALF = 25 -> 1.000 МГц (max 1.024 МГц).
|
||||||
|
MCLK_HALF : integer := 25;
|
||||||
|
|
||||||
|
-- tSETTLING = 590 x tMCLK для AD7766
|
||||||
|
SETTLING_MCLK : integer := 590;
|
||||||
|
|
||||||
|
-- Длительность низкого уровня SYNC/PD в периодах MCLK
|
||||||
|
SYNC_LOW_MCLK : integer := 8
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
rst_n : in std_logic;
|
||||||
|
|
||||||
|
-- Интерфейс с АЦП
|
||||||
|
adc_mclk : out std_logic;
|
||||||
|
adc_sync_pd_n : out std_logic;
|
||||||
|
adc_sdi : out std_logic;
|
||||||
|
adc_drdy : in std_logic;
|
||||||
|
|
||||||
|
-- Интерфейс с SPI Master
|
||||||
|
spi_busy : in std_logic;
|
||||||
|
spi_ena : out std_logic;
|
||||||
|
spi_tx_data : out std_logic_vector(23 downto 0);
|
||||||
|
spi_rx : in std_logic_vector(23 downto 0);
|
||||||
|
|
||||||
|
-- Отсчёты и статус
|
||||||
|
sample : out std_logic_vector(23 downto 0);
|
||||||
|
sample_valid : out std_logic;
|
||||||
|
ready : out std_logic; -- фильтр установился, идёт нормальный поток
|
||||||
|
err_overrun : out std_logic -- импульс: DRDY пришёл раньше конца чтения
|
||||||
|
);
|
||||||
|
end entity spi_ctrl;
|
||||||
|
|
||||||
|
architecture rtl of spi_ctrl is
|
||||||
|
|
||||||
|
type fsm_state_t is (
|
||||||
|
ST_SYNC_LOW, -- держим SYNC/PD низким
|
||||||
|
ST_SETTLE, -- ждём tSETTLING после снятия SYNC/PD
|
||||||
|
ST_IDLE, -- ждём спад DRDY
|
||||||
|
ST_READ -- идёт обмен, ждём освобождения мастера
|
||||||
|
);
|
||||||
|
signal state : fsm_state_t := ST_SYNC_LOW;
|
||||||
|
|
||||||
|
-- Генератор MCLK
|
||||||
|
signal mclk_cnt : integer range 0 to MCLK_HALF - 1 := 0;
|
||||||
|
signal mclk_s : std_logic := '0';
|
||||||
|
signal mclk_rise : std_logic := '0';
|
||||||
|
|
||||||
|
-- Детекторы фронтов
|
||||||
|
signal drdy_sync1 : std_logic := '1';
|
||||||
|
signal drdy_sync2 : std_logic := '1';
|
||||||
|
signal drdy_sync3 : std_logic := '1';
|
||||||
|
signal spi_busy_d : std_logic := '0';
|
||||||
|
|
||||||
|
signal adc_drdy_fall : std_logic;
|
||||||
|
signal spi_busy_fall : std_logic;
|
||||||
|
|
||||||
|
signal mclk_ticks : integer range 0 to SETTLING_MCLK - 1 := 0;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
adc_mclk <= mclk_s;
|
||||||
|
adc_sdi <= '0';
|
||||||
|
-- Писать в AD7766 нечего, но мастер требует слово для передачи
|
||||||
|
spi_tx_data <= (others => '0');
|
||||||
|
ready <= '0' when state = ST_SYNC_LOW or state = ST_SETTLE else '1';
|
||||||
|
-- Генератор MCLK. События SYNC/PD привязаны к нарастающим фронтам MCLK,
|
||||||
|
-- поэтому отдаём наружу ещё и однотактовый строб фронта.
|
||||||
|
mclk_proc : process (clk, rst_n)
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
mclk_cnt <= 0;
|
||||||
|
mclk_s <= '0';
|
||||||
|
mclk_rise <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
mclk_rise <= '0';
|
||||||
|
if mclk_cnt = MCLK_HALF - 1 then
|
||||||
|
mclk_cnt <= 0;
|
||||||
|
mclk_s <= not mclk_s;
|
||||||
|
if mclk_s = '0' then
|
||||||
|
mclk_rise <= '1';
|
||||||
|
end if;
|
||||||
|
else
|
||||||
|
mclk_cnt <= mclk_cnt + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process mclk_proc;
|
||||||
|
|
||||||
|
-- Детекторы фронтов
|
||||||
|
edge_proc : process (clk, rst_n)
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
drdy_sync1 <= '1';
|
||||||
|
drdy_sync2 <= '1';
|
||||||
|
drdy_sync3 <= '1';
|
||||||
|
spi_busy_d <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
drdy_sync1 <= adc_drdy;
|
||||||
|
drdy_sync2 <= drdy_sync1;
|
||||||
|
drdy_sync3 <= drdy_sync2;
|
||||||
|
spi_busy_d <= spi_busy;
|
||||||
|
end if;
|
||||||
|
end process edge_proc;
|
||||||
|
|
||||||
|
-- DRDY низкий почти весь период, наверх уходит коротким импульсом
|
||||||
|
-- t5 = 294 нс. Ловим спад: он отмечает готовность нового слова (стр. 17).
|
||||||
|
adc_drdy_fall <= '1' when drdy_sync3 = '1' and drdy_sync2 = '0' else
|
||||||
|
'0';
|
||||||
|
spi_busy_fall <= '1' when spi_busy_d = '1' and spi_busy = '0' else
|
||||||
|
'0';
|
||||||
|
|
||||||
|
-- Основной конечный автомат
|
||||||
|
fsm_proc : process (clk, rst_n)
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
state <= ST_SYNC_LOW;
|
||||||
|
mclk_ticks <= 0;
|
||||||
|
adc_sync_pd_n <= '0';
|
||||||
|
spi_ena <= '0';
|
||||||
|
sample <= (others => '0');
|
||||||
|
sample_valid <= '0';
|
||||||
|
err_overrun <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
-- Значения по умолчанию для стробов
|
||||||
|
spi_ena <= '0';
|
||||||
|
sample_valid <= '0';
|
||||||
|
err_overrun <= '0';
|
||||||
|
-- Новый DRDY во время чтения означает, что мы не уложились в окно
|
||||||
|
if adc_drdy_fall = '1' and state = ST_READ then
|
||||||
|
err_overrun <= '1';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
case state is
|
||||||
|
|
||||||
|
when ST_SYNC_LOW =>
|
||||||
|
adc_sync_pd_n <= '0';
|
||||||
|
if mclk_rise = '1' then
|
||||||
|
if mclk_ticks = SYNC_LOW_MCLK - 1 then
|
||||||
|
mclk_ticks <= 0;
|
||||||
|
state <= ST_SETTLE;
|
||||||
|
else
|
||||||
|
mclk_ticks <= mclk_ticks + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- После снятия SYNC/PD фильтру нужно tSETTLING, до этого DRDY
|
||||||
|
-- не значит ничего
|
||||||
|
when ST_SETTLE =>
|
||||||
|
adc_sync_pd_n <= '1';
|
||||||
|
if mclk_rise = '1' then
|
||||||
|
if mclk_ticks = SETTLING_MCLK - 1 then
|
||||||
|
mclk_ticks <= 0;
|
||||||
|
state <= ST_IDLE;
|
||||||
|
else
|
||||||
|
mclk_ticks <= mclk_ticks + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when ST_IDLE =>
|
||||||
|
if adc_drdy_fall = '1' then
|
||||||
|
spi_ena <= '1';
|
||||||
|
state <= ST_READ;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Мастер поднимает busy на следующем такте после enable, поэтому
|
||||||
|
-- ложного спада тут быть не может
|
||||||
|
when ST_READ =>
|
||||||
|
if spi_busy_fall = '1' then
|
||||||
|
sample <= spi_rx;
|
||||||
|
sample_valid <= '1';
|
||||||
|
state <= ST_IDLE;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
end process fsm_proc;
|
||||||
|
|
||||||
|
end architecture rtl;
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: spi_master - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description: -- Интерфейс и устройство взяты от spi_master (Scott Larson,
|
||||||
|
-- Digi-Key/eewiki): тот же автомат init/execute, те же имена сигналов
|
||||||
|
-- и портов, включая неиспользуемые здесь mosi/tx/flag_tx.
|
||||||
|
--
|
||||||
|
-- Два отличия, обязательные для AD7766:
|
||||||
|
--
|
||||||
|
-- 1. Захват первого бита по спаду CS. AD7766 выставляет MSB на SDO в момент
|
||||||
|
-- спада CS, до первого фронта SCLK (datasheet Rev. D, стр. 17, Figure 3).
|
||||||
|
-- Донорский мастер защёлкивает miso только после переключений SCLK и этот
|
||||||
|
-- бит терял, собирая слово со сдвигом. Поведение выбирается через cpha:
|
||||||
|
-- cpha = '0' -- первый бит берётся при выставлении CS (AD7766)
|
||||||
|
-- cpha = '1' -- первый бит берётся после первого фронта (как у донора)
|
||||||
|
--
|
||||||
|
-- 2. Делитель SCLK. Донор переключал SCLK каждый такт, то есть clk/2 = 25 МГц.
|
||||||
|
-- Формально в предел AD7766 это укладывается (tSCLK min = t8, Table 3),
|
||||||
|
-- но на проводах избыточно, поэтому полупериод задаётся generic'ом.
|
||||||
|
--
|
||||||
|
-- Тактирование по нарастающему фронту, а не по спаду как у донора: обмен
|
||||||
|
-- с spi_ctrl идёт по одному фронту, без путей в полтакта между модулями.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
|
||||||
|
entity spi_master is
|
||||||
|
generic (
|
||||||
|
data_length : integer := 24; -- длина слова в битах
|
||||||
|
sclk_half : integer := 4 -- полупериод SCLK в тактах clk
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
reset_n : in std_logic;
|
||||||
|
enable : in std_logic;
|
||||||
|
cpol : in std_logic;
|
||||||
|
cpha : in std_logic;
|
||||||
|
miso : in std_logic;
|
||||||
|
sclk : out std_logic;
|
||||||
|
ss_n : out std_logic;
|
||||||
|
mosi : out std_logic;
|
||||||
|
busy : out std_logic;
|
||||||
|
wr_ack : out std_logic;
|
||||||
|
flag_tx : in std_logic;
|
||||||
|
tx : in std_logic_vector(data_length - 1 downto 0);
|
||||||
|
rx : out std_logic_vector(data_length - 1 downto 0)
|
||||||
|
);
|
||||||
|
end entity spi_master;
|
||||||
|
|
||||||
|
architecture behavioural of spi_master is
|
||||||
|
|
||||||
|
type FSM is (init, execute);
|
||||||
|
signal state : FSM := init;
|
||||||
|
|
||||||
|
-- Тик = полупериод SCLK. Тик 0 отдан установлению SDO после спада CS,
|
||||||
|
-- дальше на каждый тик приходится одно переключение SCLK.
|
||||||
|
signal prescale : integer range 0 to sclk_half - 1 := 0;
|
||||||
|
signal clk_toggles : integer range 0 to data_length * 2 + 1 := 0;
|
||||||
|
signal last_toggle : integer range 0 to data_length * 2 + 1 := 0;
|
||||||
|
|
||||||
|
signal rxBuffer : std_logic_vector(data_length - 1 downto 0) := (others => '0');
|
||||||
|
signal txBuffer : std_logic_vector(data_length - 1 downto 0) := (others => '0');
|
||||||
|
|
||||||
|
signal INT_ss_n : std_logic := '1';
|
||||||
|
signal INT_sclk : std_logic := '1';
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
ss_n <= INT_ss_n;
|
||||||
|
sclk <= INT_sclk;
|
||||||
|
|
||||||
|
process (clk, reset_n)
|
||||||
|
begin
|
||||||
|
|
||||||
|
if (reset_n = '0') then
|
||||||
|
busy <= '1';
|
||||||
|
INT_ss_n <= '1';
|
||||||
|
INT_sclk <= '1';
|
||||||
|
mosi <= '0';
|
||||||
|
wr_ack <= '0';
|
||||||
|
rx <= (others => '0');
|
||||||
|
state <= init;
|
||||||
|
|
||||||
|
elsif (rising_edge(clk)) then
|
||||||
|
case state is
|
||||||
|
|
||||||
|
when init => -- шина свободна
|
||||||
|
busy <= '0';
|
||||||
|
INT_ss_n <= '1';
|
||||||
|
INT_sclk <= cpol;
|
||||||
|
wr_ack <= '0';
|
||||||
|
|
||||||
|
if (enable = '1') then
|
||||||
|
busy <= '1';
|
||||||
|
INT_ss_n <= '0'; -- CS вниз, АЦП выставляет MSB
|
||||||
|
INT_sclk <= cpol;
|
||||||
|
txBuffer <= tx;
|
||||||
|
rxBuffer <= (others => '0');
|
||||||
|
prescale <= 0;
|
||||||
|
clk_toggles <= 0;
|
||||||
|
-- При cpha='0' первый бит берётся на тике 0, поэтому переключений
|
||||||
|
-- SCLK нужно на два меньше
|
||||||
|
if cpha = '0' then
|
||||||
|
last_toggle <= data_length * 2 - 2;
|
||||||
|
else
|
||||||
|
last_toggle <= data_length * 2;
|
||||||
|
end if;
|
||||||
|
state <= execute;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
when execute =>
|
||||||
|
busy <= '1';
|
||||||
|
wr_ack <= '0';
|
||||||
|
|
||||||
|
if (prescale < sclk_half - 1) then
|
||||||
|
prescale <= prescale + 1;
|
||||||
|
else
|
||||||
|
prescale <= 0;
|
||||||
|
|
||||||
|
if (clk_toggles = 0) then
|
||||||
|
-- Тик установления: SCLK не трогаем. MSB уже на SDO, ждали
|
||||||
|
-- sclk_half тактов -- с запасом против t7 и t8
|
||||||
|
if (cpha = '0') then
|
||||||
|
rxBuffer <= rxBuffer(data_length - 2 downto 0) & miso;
|
||||||
|
end if;
|
||||||
|
clk_toggles <= 1;
|
||||||
|
|
||||||
|
elsif (clk_toggles <= last_toggle) then
|
||||||
|
INT_sclk <= not INT_sclk;
|
||||||
|
-- Данные АЦП меняет по спадам SCLK, поэтому защёлкиваем их
|
||||||
|
-- по подъёму, через полпериода после смены
|
||||||
|
if (INT_sclk = '0') then
|
||||||
|
rxBuffer <= rxBuffer(data_length - 2 downto 0) & miso;
|
||||||
|
else
|
||||||
|
mosi <= txBuffer(data_length - 1);
|
||||||
|
txBuffer <= txBuffer(data_length - 2 downto 0) & '0';
|
||||||
|
end if;
|
||||||
|
clk_toggles <= clk_toggles + 1;
|
||||||
|
|
||||||
|
else
|
||||||
|
-- Обмен завершён
|
||||||
|
INT_ss_n <= '1';
|
||||||
|
INT_sclk <= cpol;
|
||||||
|
rx <= rxBuffer;
|
||||||
|
busy <= '0';
|
||||||
|
if (flag_tx = '1') then
|
||||||
|
wr_ack <= '1';
|
||||||
|
end if;
|
||||||
|
state <= init;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
end process;
|
||||||
|
|
||||||
|
end architecture behavioural;
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: Top - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
-- Регистратор AD7766: MCLK 1.000 МГц -> ODR 125 kSPS -> дециматор sinc^1 /10
|
||||||
|
-- -> 12.5 кГц -> кадры по 50 отсчётов -> UART 921600.
|
||||||
|
-- Параметры и обоснования — в docs/design.md.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity Top is
|
||||||
|
generic (
|
||||||
|
SYS_CLK_HZ : integer := 50_000_000;
|
||||||
|
BAUD_RATE : integer := 921600
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
|
||||||
|
-- АЦП
|
||||||
|
ADC_SDO : in std_logic;
|
||||||
|
ADC_DRDY : in std_logic;
|
||||||
|
ADC_SCLK : out std_logic;
|
||||||
|
ADC_MCLK : out std_logic;
|
||||||
|
ADC_SDI : out std_logic;
|
||||||
|
ADC_CS_N : out std_logic;
|
||||||
|
ADC_SYNC_PD : out std_logic;
|
||||||
|
|
||||||
|
-- UART на внешний USB-мост
|
||||||
|
UART_TX : out std_logic;
|
||||||
|
|
||||||
|
LED : out std_logic
|
||||||
|
);
|
||||||
|
end entity Top;
|
||||||
|
|
||||||
|
architecture Behavioral of Top is
|
||||||
|
|
||||||
|
signal rst_n : std_logic := '1';
|
||||||
|
|
||||||
|
signal sample : std_logic_vector(23 downto 0);
|
||||||
|
signal sample_valid : std_logic;
|
||||||
|
signal err_overrun : std_logic;
|
||||||
|
|
||||||
|
signal dec_data : std_logic_vector(23 downto 0);
|
||||||
|
signal dec_valid : std_logic;
|
||||||
|
|
||||||
|
signal uart_ena : std_logic;
|
||||||
|
signal uart_data : std_logic_vector(7 downto 0);
|
||||||
|
signal uart_busy : std_logic;
|
||||||
|
|
||||||
|
signal spi_ena : std_logic;
|
||||||
|
signal spi_busy : std_logic;
|
||||||
|
signal spi_tx_data : std_logic_vector(23 downto 0);
|
||||||
|
signal spi_rx : std_logic_vector(23 downto 0);
|
||||||
|
|
||||||
|
signal fifo_q : std_logic_vector(23 downto 0);
|
||||||
|
signal fifo_rd_en : std_logic;
|
||||||
|
signal fifo_dready : std_logic;
|
||||||
|
signal fifo_full : std_logic;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
rst_n <= '1';
|
||||||
|
|
||||||
|
spi_master_inst : entity work.spi_master(behavioural)
|
||||||
|
generic map(
|
||||||
|
data_length => 24,
|
||||||
|
sclk_half => 4 -- 50 МГц / 8 = 6.25 МГц
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
reset_n => rst_n,
|
||||||
|
enable => spi_ena,
|
||||||
|
cpol => '1', -- SCLK в покое высокий
|
||||||
|
cpha => '0', -- MSB берётся по спаду CS
|
||||||
|
miso => ADC_SDO,
|
||||||
|
sclk => ADC_SCLK,
|
||||||
|
ss_n => ADC_CS_N,
|
||||||
|
mosi => open,
|
||||||
|
busy => spi_busy,
|
||||||
|
wr_ack => open,
|
||||||
|
flag_tx => '0',
|
||||||
|
tx => spi_tx_data,
|
||||||
|
rx => spi_rx
|
||||||
|
);
|
||||||
|
|
||||||
|
spi_ctrl_inst : entity work.spi_ctrl(rtl)
|
||||||
|
generic map(
|
||||||
|
MCLK_HALF => 25, -- 50 МГц / 50 = 1.000 МГц
|
||||||
|
SETTLING_MCLK => 590, -- tSETTLING для AD7766
|
||||||
|
SYNC_LOW_MCLK => 8
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
rst_n => rst_n,
|
||||||
|
adc_mclk => ADC_MCLK,
|
||||||
|
adc_sync_pd_n => ADC_SYNC_PD,
|
||||||
|
adc_sdi => ADC_SDI,
|
||||||
|
adc_drdy => ADC_DRDY,
|
||||||
|
spi_busy => spi_busy,
|
||||||
|
spi_ena => spi_ena,
|
||||||
|
spi_tx_data => spi_tx_data,
|
||||||
|
spi_rx => spi_rx,
|
||||||
|
sample => sample,
|
||||||
|
sample_valid => sample_valid,
|
||||||
|
ready => open,
|
||||||
|
err_overrun => err_overrun
|
||||||
|
);
|
||||||
|
|
||||||
|
decimator_inst : entity work.decimator(rtl)
|
||||||
|
generic map(
|
||||||
|
DECIM => 10,
|
||||||
|
RECIP_M => 429496730,
|
||||||
|
RECIP_SH => 32
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
rst_n => rst_n,
|
||||||
|
din => sample,
|
||||||
|
din_valid => sample_valid,
|
||||||
|
dout => dec_data,
|
||||||
|
dout_valid => dec_valid
|
||||||
|
);
|
||||||
|
|
||||||
|
fifo_top_inst : entity work.fifo_top(rtl)
|
||||||
|
generic map(
|
||||||
|
DWIDTH => 24,
|
||||||
|
DEPTH => 128,
|
||||||
|
AF_LEVEL => 50 -- порог начала кадра
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
Data => dec_data,
|
||||||
|
WrClk => clk,
|
||||||
|
RdClk => clk,
|
||||||
|
WrEn => dec_valid,
|
||||||
|
RdEn => fifo_rd_en,
|
||||||
|
Q => fifo_q,
|
||||||
|
Empty => open,
|
||||||
|
Almost_Full => fifo_dready,
|
||||||
|
Full => fifo_full
|
||||||
|
);
|
||||||
|
|
||||||
|
packets_creator_inst : entity work.Packets_creator(Behavioral)
|
||||||
|
generic map(
|
||||||
|
SAMPLES_PER_FRAME => 50
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
rst_n => rst_n,
|
||||||
|
data_ADC => fifo_q,
|
||||||
|
dready => fifo_dready,
|
||||||
|
rd_en => fifo_rd_en,
|
||||||
|
wr_en => dec_valid,
|
||||||
|
fifo_full => fifo_full,
|
||||||
|
err_overrun => err_overrun,
|
||||||
|
busy_uart => uart_busy,
|
||||||
|
ena_uart => uart_ena,
|
||||||
|
dout => uart_data,
|
||||||
|
fault => open
|
||||||
|
);
|
||||||
|
|
||||||
|
uart_inst : entity work.uart(logic)
|
||||||
|
generic map(
|
||||||
|
clk_freq => SYS_CLK_HZ,
|
||||||
|
baud_rate => BAUD_RATE,
|
||||||
|
os_rate => 16,
|
||||||
|
d_width => 8,
|
||||||
|
parity => 0,
|
||||||
|
parity_eo => '0'
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
reset_n => rst_n,
|
||||||
|
tx_ena => uart_ena,
|
||||||
|
tx_data => uart_data,
|
||||||
|
rx => '1',
|
||||||
|
rx_busy => open,
|
||||||
|
rx_error => open,
|
||||||
|
rx_data => open,
|
||||||
|
tx_busy => uart_busy,
|
||||||
|
tx => UART_TX
|
||||||
|
);
|
||||||
|
|
||||||
|
led_status_inst : entity work.led_status(rtl)
|
||||||
|
generic map(
|
||||||
|
SYS_CLK_HZ => SYS_CLK_HZ,
|
||||||
|
SLOW_HZ => 2,
|
||||||
|
FAST_HZ => 10,
|
||||||
|
ALIVE_MS => 200
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
data_event => sample_valid,
|
||||||
|
led => LED
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
end architecture Behavioral;
|
||||||
+198
@@ -0,0 +1,198 @@
|
|||||||
|
--------------------------------------------------------------------------------
|
||||||
|
--
|
||||||
|
-- FileName: uart.vhd
|
||||||
|
-- Dependencies: none
|
||||||
|
-- Design Software: Quartus II 64-bit Version 13.1.0 Build 162 SJ Web Edition
|
||||||
|
--
|
||||||
|
-- HDL CODE IS PROVIDED "AS IS." DIGI-KEY EXPRESSLY DISCLAIMS ANY
|
||||||
|
-- WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
-- PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL DIGI-KEY
|
||||||
|
-- BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL
|
||||||
|
-- DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR EQUIPMENT, COST OF
|
||||||
|
-- PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
|
||||||
|
-- BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF),
|
||||||
|
-- ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS.
|
||||||
|
--
|
||||||
|
-- Version History
|
||||||
|
-- Version 1.0 5/26/2017 Scott Larson
|
||||||
|
-- Initial Public Release
|
||||||
|
-- Version 1.1 8/3/2021 Scott Larson
|
||||||
|
-- Corrected rx start bit error checking
|
||||||
|
--
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
LIBRARY ieee;
|
||||||
|
USE ieee.std_logic_1164.all;
|
||||||
|
|
||||||
|
ENTITY uart IS
|
||||||
|
GENERIC(
|
||||||
|
clk_freq : INTEGER := 50_000_000; --frequency of system clock in Hertz
|
||||||
|
baud_rate : INTEGER := 921600; --data link baud rate in bits/second
|
||||||
|
os_rate : INTEGER := 16; --oversampling rate to find center of receive bits (in samples per baud period)
|
||||||
|
d_width : INTEGER := 8; --data bus width
|
||||||
|
parity : INTEGER := 0; --0 for no parity, 1 for parity
|
||||||
|
parity_eo : STD_LOGIC := '0'); --'0' for even, '1' for odd parity
|
||||||
|
PORT(
|
||||||
|
clk : IN STD_LOGIC; --system clock
|
||||||
|
reset_n : IN STD_LOGIC; --ascynchronous reset
|
||||||
|
tx_ena : IN STD_LOGIC; --initiate transmission
|
||||||
|
tx_data : IN STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --data to transmit
|
||||||
|
rx : IN STD_LOGIC; --receive pin
|
||||||
|
rx_busy : OUT STD_LOGIC; --data reception in progress
|
||||||
|
rx_error : OUT STD_LOGIC; --start, parity, or stop bit error detected
|
||||||
|
rx_data : OUT STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --data received
|
||||||
|
tx_busy : OUT STD_LOGIC; --transmission in progress
|
||||||
|
tx : OUT STD_LOGIC); --transmit pin
|
||||||
|
END uart;
|
||||||
|
|
||||||
|
ARCHITECTURE logic OF uart IS
|
||||||
|
TYPE tx_machine IS(idle, transmit); --tranmit state machine data type
|
||||||
|
TYPE rx_machine IS(idle, receive); --receive state machine data type
|
||||||
|
SIGNAL tx_state : tx_machine; --transmit state machine
|
||||||
|
SIGNAL rx_state : rx_machine; --receive state machine
|
||||||
|
SIGNAL baud_pulse : STD_LOGIC := '0'; --periodic pulse that occurs at the baud rate
|
||||||
|
SIGNAL os_pulse : STD_LOGIC := '0'; --periodic pulse that occurs at the oversampling rate
|
||||||
|
SIGNAL parity_error : STD_LOGIC; --receive parity error flag
|
||||||
|
SIGNAL rx_parity : STD_LOGIC_VECTOR(d_width DOWNTO 0); --calculation of receive parity
|
||||||
|
SIGNAL tx_parity : STD_LOGIC_VECTOR(d_width DOWNTO 0); --calculation of transmit parity
|
||||||
|
SIGNAL rx_buffer : STD_LOGIC_VECTOR(parity+d_width DOWNTO 0) := (OTHERS => '0'); --values received
|
||||||
|
SIGNAL tx_buffer : STD_LOGIC_VECTOR(parity+d_width+1 DOWNTO 0) := (OTHERS => '1'); --values to be transmitted
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
--generate clock enable pulses at the baud rate and the oversampling rate
|
||||||
|
PROCESS(reset_n, clk)
|
||||||
|
VARIABLE count_baud : INTEGER RANGE 0 TO clk_freq/baud_rate-1 := 0; --counter to determine baud rate period
|
||||||
|
VARIABLE count_os : INTEGER RANGE 0 TO clk_freq/baud_rate/os_rate-1 := 0; --counter to determine oversampling period
|
||||||
|
BEGIN
|
||||||
|
IF(reset_n = '0') THEN --asynchronous reset asserted
|
||||||
|
baud_pulse <= '0'; --reset baud rate pulse
|
||||||
|
os_pulse <= '0'; --reset oversampling rate pulse
|
||||||
|
count_baud := 0; --reset baud period counter
|
||||||
|
count_os := 0; --reset oversampling period counter
|
||||||
|
ELSIF(clk'EVENT AND clk = '1') THEN
|
||||||
|
--create baud enable pulse
|
||||||
|
IF(count_baud < clk_freq/baud_rate-1) THEN --baud period not reached
|
||||||
|
count_baud := count_baud + 1; --increment baud period counter
|
||||||
|
baud_pulse <= '0'; --deassert baud rate pulse
|
||||||
|
ELSE --baud period reached
|
||||||
|
count_baud := 0; --reset baud period counter
|
||||||
|
baud_pulse <= '1'; --assert baud rate pulse
|
||||||
|
count_os := 0; --reset oversampling period counter to avoid cumulative error
|
||||||
|
END IF;
|
||||||
|
--create oversampling enable pulse
|
||||||
|
IF(count_os < clk_freq/baud_rate/os_rate-1) THEN --oversampling period not reached
|
||||||
|
count_os := count_os + 1; --increment oversampling period counter
|
||||||
|
os_pulse <= '0'; --deassert oversampling rate pulse
|
||||||
|
ELSE --oversampling period reached
|
||||||
|
count_os := 0; --reset oversampling period counter
|
||||||
|
os_pulse <= '1'; --assert oversampling pulse
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
END PROCESS;
|
||||||
|
|
||||||
|
--receive state machine
|
||||||
|
PROCESS(reset_n, clk)
|
||||||
|
VARIABLE rx_count : INTEGER RANGE 0 TO parity+d_width+2 := 0; --count the bits received
|
||||||
|
VARIABLE os_count : INTEGER RANGE 0 TO os_rate-1 := 0; --count the oversampling rate pulses
|
||||||
|
BEGIN
|
||||||
|
IF(reset_n = '0') THEN --asynchronous reset asserted
|
||||||
|
os_count := 0; --clear oversampling pulse counter
|
||||||
|
rx_count := 0; --clear receive bit counter
|
||||||
|
rx_busy <= '0'; --clear receive busy signal
|
||||||
|
rx_error <= '0'; --clear receive errors
|
||||||
|
rx_data <= (OTHERS => '0'); --clear received data output
|
||||||
|
rx_state <= idle; --put in idle state
|
||||||
|
ELSIF(clk'EVENT AND clk = '1' AND os_pulse = '1') THEN --enable clock at oversampling rate
|
||||||
|
CASE rx_state IS
|
||||||
|
WHEN idle => --idle state
|
||||||
|
rx_busy <= '0'; --clear receive busy flag
|
||||||
|
IF(rx = '0') THEN --start bit might be present
|
||||||
|
IF(os_count < os_rate/2) THEN --oversampling pulse counter is not at start bit center
|
||||||
|
os_count := os_count + 1; --increment oversampling pulse counter
|
||||||
|
rx_state <= idle; --remain in idle state
|
||||||
|
ELSE --oversampling pulse counter is at bit center
|
||||||
|
os_count := 0; --clear oversampling pulse counter
|
||||||
|
rx_count := 0; --clear the bits received counter
|
||||||
|
rx_busy <= '1'; --assert busy flag
|
||||||
|
rx_buffer <= rx & rx_buffer(parity+d_width DOWNTO 1); --shift the start bit into receive buffer
|
||||||
|
rx_state <= receive; --advance to receive state
|
||||||
|
END IF;
|
||||||
|
ELSE --start bit not present
|
||||||
|
os_count := 0; --clear oversampling pulse counter
|
||||||
|
rx_state <= idle; --remain in idle state
|
||||||
|
END IF;
|
||||||
|
WHEN receive => --receive state
|
||||||
|
IF(os_count < os_rate-1) THEN --not center of bit
|
||||||
|
os_count := os_count + 1; --increment oversampling pulse counter
|
||||||
|
rx_state <= receive; --remain in receive state
|
||||||
|
ELSIF(rx_count < parity+d_width) THEN --center of bit and not all bits received
|
||||||
|
os_count := 0; --reset oversampling pulse counter
|
||||||
|
rx_count := rx_count + 1; --increment number of bits received counter
|
||||||
|
rx_buffer <= rx & rx_buffer(parity+d_width DOWNTO 1); --shift new received bit into receive buffer
|
||||||
|
rx_state <= receive; --remain in receive state
|
||||||
|
ELSE --center of stop bit
|
||||||
|
rx_data <= rx_buffer(d_width DOWNTO 1); --output data received to user logic
|
||||||
|
rx_error <= rx_buffer(0) OR parity_error OR NOT rx; --output start, parity, and stop bit error flag
|
||||||
|
rx_busy <= '0'; --deassert received busy flag
|
||||||
|
rx_state <= idle; --return to idle state
|
||||||
|
END IF;
|
||||||
|
END CASE;
|
||||||
|
END IF;
|
||||||
|
END PROCESS;
|
||||||
|
|
||||||
|
--receive parity calculation logic
|
||||||
|
rx_parity(0) <= parity_eo;
|
||||||
|
rx_parity_logic: FOR i IN 0 to d_width-1 GENERATE
|
||||||
|
rx_parity(i+1) <= rx_parity(i) XOR rx_buffer(i+1);
|
||||||
|
END GENERATE;
|
||||||
|
WITH parity SELECT --compare calculated parity bit with received parity bit to determine error
|
||||||
|
parity_error <= rx_parity(d_width) XOR rx_buffer(parity+d_width) WHEN 1, --using parity
|
||||||
|
'0' WHEN OTHERS; --not using parity
|
||||||
|
|
||||||
|
--transmit state machine
|
||||||
|
PROCESS(reset_n, clk)
|
||||||
|
VARIABLE tx_count : INTEGER RANGE 0 TO parity+d_width+3 := 0; --count bits transmitted
|
||||||
|
BEGIN
|
||||||
|
IF(reset_n = '0') THEN --asynchronous reset asserted
|
||||||
|
tx_count := 0; --clear transmit bit counter
|
||||||
|
tx <= '1'; --set tx pin to idle value of high
|
||||||
|
tx_busy <= '1'; --set transmit busy signal to indicate unavailable
|
||||||
|
tx_state <= idle; --set tx state machine to ready state
|
||||||
|
ELSIF(clk'EVENT AND clk = '1') THEN
|
||||||
|
CASE tx_state IS
|
||||||
|
WHEN idle => --idle state
|
||||||
|
IF(tx_ena = '1') THEN --new transaction latched in
|
||||||
|
tx_buffer(d_width+1 DOWNTO 0) <= tx_data & '0' & '1'; --latch in data for transmission and start/stop bits
|
||||||
|
IF(parity = 1) THEN --if parity is used
|
||||||
|
tx_buffer(parity+d_width+1) <= tx_parity(d_width); --latch in parity bit from parity logic
|
||||||
|
END IF;
|
||||||
|
tx_busy <= '1'; --assert transmit busy flag
|
||||||
|
tx_count := 0; --clear transmit bit count
|
||||||
|
tx_state <= transmit; --proceed to transmit state
|
||||||
|
ELSE --no new transaction initiated
|
||||||
|
tx_busy <= '0'; --clear transmit busy flag
|
||||||
|
tx_state <= idle; --remain in idle state
|
||||||
|
END IF;
|
||||||
|
WHEN transmit => --transmit state
|
||||||
|
IF(baud_pulse = '1') THEN --beginning of bit
|
||||||
|
tx_count := tx_count + 1; --increment transmit bit counter
|
||||||
|
tx_buffer <= '1' & tx_buffer(parity+d_width+1 DOWNTO 1); --shift transmit buffer to output next bit
|
||||||
|
END IF;
|
||||||
|
IF(tx_count < parity+d_width+3) THEN --not all bits transmitted
|
||||||
|
tx_state <= transmit; --remain in transmit state
|
||||||
|
ELSE --all bits transmitted
|
||||||
|
tx_state <= idle; --return to idle state
|
||||||
|
END IF;
|
||||||
|
END CASE;
|
||||||
|
tx <= tx_buffer(0); --output last bit in transmit transaction buffer
|
||||||
|
END IF;
|
||||||
|
END PROCESS;
|
||||||
|
|
||||||
|
--transmit parity calculation logic
|
||||||
|
tx_parity(0) <= parity_eo;
|
||||||
|
tx_parity_logic: FOR i IN 0 to d_width-1 GENERATE
|
||||||
|
tx_parity(i+1) <= tx_parity(i) XOR tx_data(i);
|
||||||
|
END GENERATE;
|
||||||
|
|
||||||
|
END logic;
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: decimator - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
-- Дециматор sinc^1: среднее по DECIM отсчётам. Понижает 125 kSPS с АЦП
|
||||||
|
-- до целевого 12.5 кГц
|
||||||
|
-- АЧХ (DECIM=10, вход 125 кГц): -0.09 дБ на 1 кГц, -0.83 дБ на 3 кГц,
|
||||||
|
-- -3.9 дБ на 6.25 кГц.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity decimator is
|
||||||
|
generic (
|
||||||
|
DECIM : integer := 10;
|
||||||
|
-- Деление на DECIM выполняется умножением на обратную величину:
|
||||||
|
-- x / DECIM = (x * RECIP_M) >> RECIP_SH, RECIP_M = ceil(2^RECIP_SH / DECIM)
|
||||||
|
-- Для DECIM=10 точность проверена перебором всего диапазона сумм (до 10*2^23).
|
||||||
|
-- При смене DECIM обе константы пересчитать.
|
||||||
|
RECIP_M : integer := 429496730; -- ceil(2^32 / 10) = 0x1999999A
|
||||||
|
RECIP_SH : integer := 32
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
rst_n : in std_logic;
|
||||||
|
|
||||||
|
din : in std_logic_vector(23 downto 0);
|
||||||
|
din_valid : in std_logic;
|
||||||
|
|
||||||
|
dout : out std_logic_vector(23 downto 0);
|
||||||
|
dout_valid : out std_logic
|
||||||
|
);
|
||||||
|
end entity decimator;
|
||||||
|
|
||||||
|
architecture rtl of decimator is
|
||||||
|
|
||||||
|
-- Сумма DECIM отсчётов по 24 бита: 24 + ceil(log2(DECIM)) = 28 бит
|
||||||
|
constant ACC_W : integer := 28;
|
||||||
|
constant M_W : integer := 30; -- разрядность RECIP_M
|
||||||
|
|
||||||
|
-- Начальные значения заданы явно: сброса в проекте нет (см. top.vhd)
|
||||||
|
signal acc : signed(ACC_W - 1 downto 0) := (others => '0');
|
||||||
|
signal cnt : integer range 0 to DECIM - 1 := 0;
|
||||||
|
|
||||||
|
signal sum_r : signed(ACC_W - 1 downto 0) := (others => '0');
|
||||||
|
signal sum_vld : std_logic := '0';
|
||||||
|
|
||||||
|
signal mag : unsigned(ACC_W - 1 downto 0) := (others => '0');
|
||||||
|
signal neg : std_logic := '0';
|
||||||
|
signal mag_vld : std_logic := '0';
|
||||||
|
|
||||||
|
signal prod : unsigned(ACC_W + M_W - 1 downto 0) := (others => '0');
|
||||||
|
signal neg_d : std_logic := '0';
|
||||||
|
signal prod_vld : std_logic := '0';
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
acc_proc : process (clk, rst_n)
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
acc <= (others => '0');
|
||||||
|
cnt <= 0;
|
||||||
|
sum_r <= (others => '0');
|
||||||
|
sum_vld <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
sum_vld <= '0';
|
||||||
|
if din_valid = '1' then
|
||||||
|
if cnt = DECIM - 1 then
|
||||||
|
sum_r <= acc + resize(signed(din), ACC_W);
|
||||||
|
sum_vld <= '1';
|
||||||
|
acc <= (others => '0');
|
||||||
|
cnt <= 0;
|
||||||
|
else
|
||||||
|
acc <= acc + resize(signed(din), ACC_W);
|
||||||
|
cnt <= cnt + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process acc_proc;
|
||||||
|
|
||||||
|
-- Делим модуль и возвращаем знак: усечение получается симметричным
|
||||||
|
-- относительно нуля, без смещения на переходе через ноль
|
||||||
|
div_proc : process (clk, rst_n)
|
||||||
|
variable q : unsigned(ACC_W + M_W - 1 - RECIP_SH downto 0);
|
||||||
|
begin
|
||||||
|
if rst_n = '0' then
|
||||||
|
mag <= (others => '0');
|
||||||
|
neg <= '0';
|
||||||
|
mag_vld <= '0';
|
||||||
|
prod <= (others => '0');
|
||||||
|
neg_d <= '0';
|
||||||
|
prod_vld <= '0';
|
||||||
|
dout <= (others => '0');
|
||||||
|
dout_valid <= '0';
|
||||||
|
elsif rising_edge(clk) then
|
||||||
|
mag_vld <= sum_vld;
|
||||||
|
prod_vld <= mag_vld;
|
||||||
|
dout_valid <= prod_vld;
|
||||||
|
|
||||||
|
if sum_r(ACC_W - 1) = '1' then
|
||||||
|
mag <= unsigned(-sum_r);
|
||||||
|
neg <= '1';
|
||||||
|
else
|
||||||
|
mag <= unsigned(sum_r);
|
||||||
|
neg <= '0';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
prod <= mag * to_unsigned(RECIP_M, M_W);
|
||||||
|
neg_d <= neg;
|
||||||
|
|
||||||
|
q := prod(prod'high downto RECIP_SH);
|
||||||
|
if neg_d = '1' then
|
||||||
|
dout <= std_logic_vector(resize(-signed('0' & q), 24));
|
||||||
|
else
|
||||||
|
dout <= std_logic_vector(resize(signed('0' & q), 24));
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process div_proc;
|
||||||
|
|
||||||
|
end architecture rtl;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: fifo_top - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity fifo_top is
|
||||||
|
generic (
|
||||||
|
DWIDTH : natural := 24; -- разрядность слова
|
||||||
|
DEPTH : natural := 128; -- глубина
|
||||||
|
AF_LEVEL : natural := 50 -- порог Almost_Full
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
Data : in std_logic_vector(DWIDTH - 1 downto 0);
|
||||||
|
WrClk : in std_logic;
|
||||||
|
RdClk : in std_logic;
|
||||||
|
WrEn : in std_logic;
|
||||||
|
RdEn : in std_logic;
|
||||||
|
Q : out std_logic_vector(DWIDTH - 1 downto 0);
|
||||||
|
Empty : out std_logic;
|
||||||
|
Almost_Full : out std_logic;
|
||||||
|
Full : out std_logic
|
||||||
|
);
|
||||||
|
end entity fifo_top;
|
||||||
|
|
||||||
|
architecture rtl of fifo_top is
|
||||||
|
|
||||||
|
type mem_t is array (0 to DEPTH - 1) of std_logic_vector(DWIDTH - 1 downto 0);
|
||||||
|
signal mem : mem_t;
|
||||||
|
|
||||||
|
signal wr_ptr : integer range 0 to DEPTH - 1 := 0;
|
||||||
|
signal rd_ptr : integer range 0 to DEPTH - 1 := 0;
|
||||||
|
signal count : integer range 0 to DEPTH := 0;
|
||||||
|
|
||||||
|
signal full_i : std_logic;
|
||||||
|
signal empty_i : std_logic;
|
||||||
|
signal wr_ok : std_logic;
|
||||||
|
signal rd_ok : std_logic;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
full_i <= '1' when count = DEPTH else '0';
|
||||||
|
empty_i <= '1' when count = 0 else '0';
|
||||||
|
|
||||||
|
Full <= full_i;
|
||||||
|
Empty <= empty_i;
|
||||||
|
Almost_Full <= '1' when count >= AF_LEVEL else '0';
|
||||||
|
|
||||||
|
-- Запись при полном буфере и чтение при пустом игнорируются, как у ядра
|
||||||
|
wr_ok <= WrEn and not full_i;
|
||||||
|
rd_ok <= RdEn and not empty_i;
|
||||||
|
|
||||||
|
-- Содержимое памяти намеренно не сбрасывается: сброс массива не даёт
|
||||||
|
-- синтезатору вывести блочную память
|
||||||
|
mem_proc : process (WrClk)
|
||||||
|
begin
|
||||||
|
if rising_edge(WrClk) then
|
||||||
|
if wr_ok = '1' then
|
||||||
|
mem(wr_ptr) <= Data;
|
||||||
|
end if;
|
||||||
|
-- Q меняется только по состоявшемуся чтению: со следующего такта
|
||||||
|
-- после RdEn на нём лежит очередное слово
|
||||||
|
if rd_ok = '1' then
|
||||||
|
Q <= mem(rd_ptr);
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process mem_proc;
|
||||||
|
|
||||||
|
ptr_proc : process (WrClk)
|
||||||
|
begin
|
||||||
|
if rising_edge(WrClk) then
|
||||||
|
if wr_ok = '1' then
|
||||||
|
if wr_ptr = DEPTH - 1 then
|
||||||
|
wr_ptr <= 0;
|
||||||
|
else
|
||||||
|
wr_ptr <= wr_ptr + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if rd_ok = '1' then
|
||||||
|
if rd_ptr = DEPTH - 1 then
|
||||||
|
rd_ptr <= 0;
|
||||||
|
else
|
||||||
|
rd_ptr <= rd_ptr + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if wr_ok = '1' and rd_ok = '0' then
|
||||||
|
count <= count + 1;
|
||||||
|
elsif wr_ok = '0' and rd_ok = '1' then
|
||||||
|
count <= count - 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process ptr_proc;
|
||||||
|
|
||||||
|
end architecture rtl;
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: led_status - Behavioral
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description: Индикация состояния тракта.
|
||||||
|
-- не меняется — тактирования на clk нет вообще
|
||||||
|
-- медленно — тактирование есть, отсчёты от АЦП не приходят
|
||||||
|
-- быстро — АЦП отвечает, отсчёты идут. Значит как минимум работает DRDY ADC.
|
||||||
|
--
|
||||||
|
-- Сброс намеренно не используется: мигание должно доказывать наличие клока
|
||||||
|
-- даже если остальной тракт по какой-то причине стоит.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
|
||||||
|
entity led_status is
|
||||||
|
generic (
|
||||||
|
SYS_CLK_HZ : integer := 50_000_000;
|
||||||
|
SLOW_HZ : integer := 2; -- нет отсчётов
|
||||||
|
FAST_HZ : integer := 10; -- отсчёты идут
|
||||||
|
ALIVE_MS : integer := 200 -- за какое время без отсчётов считаем, что их нет
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
data_event : in std_logic; -- строб прихода отсчёта
|
||||||
|
led : out std_logic
|
||||||
|
);
|
||||||
|
end entity led_status;
|
||||||
|
|
||||||
|
architecture rtl of led_status is
|
||||||
|
|
||||||
|
constant HALF_SLOW : integer := SYS_CLK_HZ / (2 * SLOW_HZ);
|
||||||
|
constant HALF_FAST : integer := SYS_CLK_HZ / (2 * FAST_HZ);
|
||||||
|
constant ALIVE_TMO : integer := (SYS_CLK_HZ / 1000) * ALIVE_MS;
|
||||||
|
|
||||||
|
signal alive_cnt : integer range 0 to ALIVE_TMO := 0;
|
||||||
|
signal data_alive : std_logic := '0';
|
||||||
|
signal blk_cnt : integer range 0 to HALF_SLOW - 1 := 0;
|
||||||
|
signal blink : std_logic := '0';
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
led <= blink;
|
||||||
|
|
||||||
|
led_proc : process (clk)
|
||||||
|
variable half : integer range 0 to HALF_SLOW;
|
||||||
|
begin
|
||||||
|
if rising_edge(clk) then
|
||||||
|
if data_event = '1' then
|
||||||
|
alive_cnt <= 0;
|
||||||
|
data_alive <= '1';
|
||||||
|
elsif alive_cnt = ALIVE_TMO then
|
||||||
|
data_alive <= '0';
|
||||||
|
else
|
||||||
|
alive_cnt <= alive_cnt + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if data_alive = '1' then
|
||||||
|
half := HALF_FAST;
|
||||||
|
else
|
||||||
|
half := HALF_SLOW;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if blk_cnt >= half - 1 then
|
||||||
|
blk_cnt <= 0;
|
||||||
|
blink <= not blink;
|
||||||
|
else
|
||||||
|
blk_cnt <= blk_cnt + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process led_proc;
|
||||||
|
|
||||||
|
end architecture rtl;
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Module Name: ad7766_model
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Description:
|
||||||
|
-- Поведенческая модель AD7766 для симуляции. Не синтезируется.
|
||||||
|
-- Воспроизводит: сброс фильтра по SYNC/PD, задержку tSETTLING, период DRDY
|
||||||
|
-- в 8 периодов MCLK, выдачу MSB по спаду CS и остальных бит по спадам SCLK.
|
||||||
|
-- Времена tN — по AD7766 Data Sheet Rev. D, Table 3.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity ad7766_model is
|
||||||
|
generic (
|
||||||
|
SETTLING_MCLK : integer := 590; -- tSETTLING для AD7766, Table 7
|
||||||
|
ODR_DIV : integer := 8; -- ODR = MCLK/8 для AD7766, Table 6
|
||||||
|
T4 : time := 265 ns;
|
||||||
|
T5 : time := 294 ns;
|
||||||
|
T7 : time := 6 ns;
|
||||||
|
T8 : time := 24 ns
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
mclk : in std_logic;
|
||||||
|
sync_pd_n : in std_logic;
|
||||||
|
cs_n : in std_logic;
|
||||||
|
sclk : in std_logic;
|
||||||
|
next_word : in std_logic_vector(23 downto 0);
|
||||||
|
|
||||||
|
sdo : out std_logic;
|
||||||
|
drdy : out std_logic;
|
||||||
|
word_tick : out std_logic -- строб: слово защёлкнуто в выходной регистр
|
||||||
|
);
|
||||||
|
end entity ad7766_model;
|
||||||
|
|
||||||
|
architecture model of ad7766_model is
|
||||||
|
signal out_reg : std_logic_vector(23 downto 0) := (others => '0');
|
||||||
|
signal bit_idx : integer range 0 to 23 := 23;
|
||||||
|
signal settled : std_logic := '0';
|
||||||
|
signal s_cnt : integer := 0;
|
||||||
|
signal div : integer range 0 to ODR_DIV - 1 := 0;
|
||||||
|
begin
|
||||||
|
|
||||||
|
drdy_proc : process (mclk, sync_pd_n)
|
||||||
|
begin
|
||||||
|
if sync_pd_n = '0' then
|
||||||
|
-- SYNC/PD низкий: выходной регистр недействителен, DRDY уходит вверх
|
||||||
|
drdy <= '1';
|
||||||
|
settled <= '0';
|
||||||
|
s_cnt <= 0;
|
||||||
|
div <= 0;
|
||||||
|
word_tick <= '0';
|
||||||
|
elsif rising_edge(mclk) then
|
||||||
|
word_tick <= '0';
|
||||||
|
if settled = '0' then
|
||||||
|
if s_cnt = SETTLING_MCLK - 1 then
|
||||||
|
settled <= '1';
|
||||||
|
div <= 0;
|
||||||
|
out_reg <= next_word;
|
||||||
|
word_tick <= '1';
|
||||||
|
drdy <= '1' after T4, '0' after T4 + T5;
|
||||||
|
else
|
||||||
|
s_cnt <= s_cnt + 1;
|
||||||
|
end if;
|
||||||
|
else
|
||||||
|
if div = ODR_DIV - 1 then
|
||||||
|
div <= 0;
|
||||||
|
out_reg <= next_word;
|
||||||
|
word_tick <= '1';
|
||||||
|
drdy <= '1' after T4, '0' after T4 + T5;
|
||||||
|
else
|
||||||
|
div <= div + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process drdy_proc;
|
||||||
|
|
||||||
|
-- MSB встаёт на SDO по спаду CS, остальные биты — спадами SCLK (стр. 17)
|
||||||
|
shift_proc : process (cs_n, sclk)
|
||||||
|
begin
|
||||||
|
if cs_n = '1' then
|
||||||
|
bit_idx <= 23;
|
||||||
|
elsif falling_edge(sclk) then
|
||||||
|
if bit_idx > 0 then
|
||||||
|
bit_idx <= bit_idx - 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process shift_proc;
|
||||||
|
|
||||||
|
sdo <= 'Z' after T7 when cs_n = '1' else
|
||||||
|
out_reg(bit_idx) after T8;
|
||||||
|
|
||||||
|
end architecture model;
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Module Name: tb_top
|
||||||
|
-- Project Name: AD7766_prj
|
||||||
|
-- Description:
|
||||||
|
-- Сквозной тест: модель AD7766 -> top -> UART -> разбор кадра в тестбенче.
|
||||||
|
-- Модель выдаёт группами по DECIM одинаковых слов, поэтому усреднённый
|
||||||
|
-- отсчёт обязан в точности совпасть со значением группы. Проверяются
|
||||||
|
-- разрядность, знак, порядок байт, CRC и счётчик кадров.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
use std.env.all;
|
||||||
|
|
||||||
|
entity tb_top is
|
||||||
|
generic (
|
||||||
|
FRAMES : integer := 2 -- сколько кадров проверяем, переопределяется -gFRAMES=N
|
||||||
|
);
|
||||||
|
end entity tb_top;
|
||||||
|
|
||||||
|
architecture sim of tb_top is
|
||||||
|
|
||||||
|
constant CLK_T : time := 20 ns;
|
||||||
|
constant BAUD : integer := 921600;
|
||||||
|
constant BIT_T : time := 1 sec / BAUD;
|
||||||
|
constant DECIM : integer := 10;
|
||||||
|
constant SPF : integer := 50; -- отсчётов в кадре
|
||||||
|
|
||||||
|
constant PAYLOAD_LEN : integer := 2 + 1 + SPF * 3; -- CNT + STATUS + DATA
|
||||||
|
|
||||||
|
signal clk : std_logic := '0';
|
||||||
|
signal run : boolean := true;
|
||||||
|
|
||||||
|
signal adc_sdo : std_logic;
|
||||||
|
signal adc_drdy : std_logic;
|
||||||
|
signal adc_sclk : std_logic;
|
||||||
|
signal adc_mclk : std_logic;
|
||||||
|
signal adc_sdi : std_logic;
|
||||||
|
signal adc_cs_n : std_logic;
|
||||||
|
signal adc_sync_pd : std_logic;
|
||||||
|
signal uart_tx : std_logic;
|
||||||
|
signal led : std_logic;
|
||||||
|
|
||||||
|
signal next_word : std_logic_vector(23 downto 0);
|
||||||
|
signal word_tick : std_logic;
|
||||||
|
signal word_idx : integer := 0;
|
||||||
|
|
||||||
|
type byte_arr_t is array (natural range <>) of std_logic_vector(7 downto 0);
|
||||||
|
|
||||||
|
-- Значения групп: крайние точки шкалы, единицы младшего разряда и оба знака
|
||||||
|
function grp_value(g : integer) return signed is
|
||||||
|
variable v : integer;
|
||||||
|
begin
|
||||||
|
case g mod 8 is
|
||||||
|
when 0 => v := 0;
|
||||||
|
when 1 => v := 8388607; -- +полная шкала
|
||||||
|
when 2 => v := - 8388608; -- -полная шкала
|
||||||
|
when 3 => v := 1;
|
||||||
|
when 4 => v := - 1;
|
||||||
|
when 5 => v := 5592405; -- 0x555555
|
||||||
|
when 6 => v := - 5592406; -- 0xAAAAAA как знаковое
|
||||||
|
when others => v := 1193046; -- 0x123456
|
||||||
|
end case;
|
||||||
|
return to_signed(v, 24);
|
||||||
|
end function;
|
||||||
|
|
||||||
|
function crc16_upd(crc : std_logic_vector(15 downto 0);
|
||||||
|
d : std_logic_vector(7 downto 0))
|
||||||
|
return std_logic_vector is
|
||||||
|
variable c : std_logic_vector(15 downto 0) := crc;
|
||||||
|
variable fb : std_logic;
|
||||||
|
begin
|
||||||
|
for i in 7 downto 0 loop
|
||||||
|
fb := c(15) xor d(i);
|
||||||
|
c := c(14 downto 0) & '0';
|
||||||
|
if fb = '1' then
|
||||||
|
c := c xor x"1021";
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
return c;
|
||||||
|
end function;
|
||||||
|
|
||||||
|
procedure get_byte(signal rx : in std_logic;
|
||||||
|
b : out std_logic_vector(7 downto 0)) is
|
||||||
|
variable v : std_logic_vector(7 downto 0);
|
||||||
|
begin
|
||||||
|
wait until rx = '0'; -- фронт старт-бита
|
||||||
|
wait for BIT_T / 2;
|
||||||
|
assert rx = '0' report "start bit not confirmed" severity error;
|
||||||
|
for i in 0 to 7 loop
|
||||||
|
wait for BIT_T;
|
||||||
|
v(i) := rx;
|
||||||
|
end loop;
|
||||||
|
wait for BIT_T;
|
||||||
|
assert rx = '1' report "stop bit not at expected position" severity error;
|
||||||
|
b := v;
|
||||||
|
end procedure;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
clk <= not clk after CLK_T / 2 when run else '0';
|
||||||
|
|
||||||
|
next_word <= std_logic_vector(grp_value(word_idx / DECIM));
|
||||||
|
|
||||||
|
word_cnt_proc : process
|
||||||
|
begin
|
||||||
|
wait until rising_edge(word_tick);
|
||||||
|
word_idx <= word_idx + 1;
|
||||||
|
end process word_cnt_proc;
|
||||||
|
|
||||||
|
dut : entity work.Top(Behavioral)
|
||||||
|
generic map(
|
||||||
|
SYS_CLK_HZ => 50_000_000,
|
||||||
|
BAUD_RATE => BAUD
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
ADC_SDO => adc_sdo,
|
||||||
|
ADC_DRDY => adc_drdy,
|
||||||
|
ADC_SCLK => adc_sclk,
|
||||||
|
ADC_MCLK => adc_mclk,
|
||||||
|
ADC_SDI => adc_sdi,
|
||||||
|
ADC_CS_N => adc_cs_n,
|
||||||
|
ADC_SYNC_PD => adc_sync_pd,
|
||||||
|
UART_TX => uart_tx,
|
||||||
|
LED => led
|
||||||
|
);
|
||||||
|
|
||||||
|
adc : entity work.ad7766_model(model)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
mclk => adc_mclk,
|
||||||
|
sync_pd_n => adc_sync_pd,
|
||||||
|
cs_n => adc_cs_n,
|
||||||
|
sclk => adc_sclk,
|
||||||
|
next_word => next_word,
|
||||||
|
sdo => adc_sdo,
|
||||||
|
drdy => adc_drdy,
|
||||||
|
word_tick => word_tick
|
||||||
|
);
|
||||||
|
|
||||||
|
check_proc : process
|
||||||
|
variable b : std_logic_vector(7 downto 0);
|
||||||
|
variable prev : std_logic_vector(7 downto 0) := (others => '0');
|
||||||
|
variable pl : byte_arr_t(0 to PAYLOAD_LEN - 1);
|
||||||
|
variable crc : std_logic_vector(15 downto 0);
|
||||||
|
variable crc_rx : std_logic_vector(15 downto 0);
|
||||||
|
variable cnt_rx : integer;
|
||||||
|
variable got : signed(23 downto 0);
|
||||||
|
variable want : signed(23 downto 0);
|
||||||
|
variable errors : integer := 0;
|
||||||
|
variable frame_no : integer := 0;
|
||||||
|
begin
|
||||||
|
report "start: waiting for first frame";
|
||||||
|
|
||||||
|
while frame_no < FRAMES loop
|
||||||
|
|
||||||
|
-- Ищем SYNC A5 5A, как это будет делать приёмник на ПК
|
||||||
|
loop
|
||||||
|
get_byte(uart_tx, b);
|
||||||
|
exit when prev = x"A5" and b = x"5A";
|
||||||
|
prev := b;
|
||||||
|
end loop;
|
||||||
|
prev := (others => '0');
|
||||||
|
|
||||||
|
for i in 0 to PAYLOAD_LEN - 1 loop
|
||||||
|
get_byte(uart_tx, b);
|
||||||
|
pl(i) := b;
|
||||||
|
end loop;
|
||||||
|
get_byte(uart_tx, b);
|
||||||
|
crc_rx(15 downto 8) := b;
|
||||||
|
get_byte(uart_tx, b);
|
||||||
|
crc_rx(7 downto 0) := b;
|
||||||
|
|
||||||
|
crc := x"FFFF";
|
||||||
|
for i in 0 to PAYLOAD_LEN - 1 loop
|
||||||
|
crc := crc16_upd(crc, pl(i));
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
if crc /= crc_rx then
|
||||||
|
report "frame " & integer'image(frame_no) & ": CRC mismatch" severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
cnt_rx := to_integer(unsigned(std_logic_vector'(pl(0) & pl(1))));
|
||||||
|
if cnt_rx /= frame_no then
|
||||||
|
report "frame " & integer'image(frame_no) & ": FRAME_CNT = " &
|
||||||
|
integer'image(cnt_rx) severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if pl(2) /= x"00" then
|
||||||
|
report "frame " & integer'image(frame_no) & ": STATUS not zero" severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
for i in 0 to SPF - 1 loop
|
||||||
|
got := signed(std_logic_vector'(pl(3 + i * 3) & pl(4 + i * 3) & pl(5 + i * 3)));
|
||||||
|
want := grp_value(frame_no * SPF + i);
|
||||||
|
if got /= want then
|
||||||
|
report "frame " & integer'image(frame_no) & " sample " & integer'image(i) &
|
||||||
|
": got " & integer'image(to_integer(got)) &
|
||||||
|
", want " & integer'image(to_integer(want)) severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
report "frame " & integer'image(frame_no) & " parsed OK";
|
||||||
|
frame_no := frame_no + 1;
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
if errors = 0 then
|
||||||
|
report "TEST PASSED: frames = " & integer'image(FRAMES) & "";
|
||||||
|
else
|
||||||
|
report "TEST FAILED: errors = " & integer'image(errors) severity error;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
run <= false;
|
||||||
|
stop;
|
||||||
|
end process check_proc;
|
||||||
|
|
||||||
|
end architecture sim;
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Приём кадров AD7766 по UART.
|
||||||
|
|
||||||
|
Пишет два файла:
|
||||||
|
<out>.raw все байты из порта, до разбора -- отладка канала
|
||||||
|
<out>.bin только отсчёты, 3 байта BE -- читается read_24bit_bin()
|
||||||
|
из F:\\PyPrj\\Signal_analayzer
|
||||||
|
|
||||||
|
Кадр с несошедшейся CRC отбрасывается, пропуск восстанавливается по FRAME_CNT
|
||||||
|
и добивается нулями, чтобы не сдвинулась ось времени.
|
||||||
|
Формат кадра -- docs/design.md.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import serial
|
||||||
|
from serial.tools import list_ports
|
||||||
|
|
||||||
|
SYNC = b"\xA5\x5A"
|
||||||
|
SAMPLES_PER_FRAME = 50
|
||||||
|
PAYLOAD_LEN = 2 + 1 + SAMPLES_PER_FRAME * 3 # FRAME_CNT + STATUS + DATA
|
||||||
|
FRAME_LEN = len(SYNC) + PAYLOAD_LEN + 2 # + CRC16
|
||||||
|
FS_DEFAULT = 12500.0
|
||||||
|
V_REF = 5.0
|
||||||
|
|
||||||
|
|
||||||
|
def _make_crc_table():
|
||||||
|
tbl = []
|
||||||
|
for b in range(256):
|
||||||
|
c = b << 8
|
||||||
|
for _ in range(8):
|
||||||
|
c = ((c << 1) ^ 0x1021) & 0xFFFF if c & 0x8000 else (c << 1) & 0xFFFF
|
||||||
|
tbl.append(c)
|
||||||
|
return tbl
|
||||||
|
|
||||||
|
|
||||||
|
_CRC_TBL = _make_crc_table()
|
||||||
|
|
||||||
|
|
||||||
|
def crc16_ccitt(data: bytes) -> int:
|
||||||
|
"""CRC-16/CCITT, poly 0x1021, init 0xFFFF, MSB-first -- как в ПЛИС."""
|
||||||
|
crc = 0xFFFF
|
||||||
|
for byte in data:
|
||||||
|
crc = ((crc << 8) & 0xFFFF) ^ _CRC_TBL[(crc >> 8) ^ byte]
|
||||||
|
return crc
|
||||||
|
|
||||||
|
|
||||||
|
class Stats:
|
||||||
|
def __init__(self):
|
||||||
|
self.frames_ok = 0
|
||||||
|
self.crc_errors = 0
|
||||||
|
self.resyncs = 0
|
||||||
|
self.padded_frames = 0
|
||||||
|
self.status_flags = 0
|
||||||
|
self.samples = 0
|
||||||
|
self.code_min = None
|
||||||
|
self.code_max = None
|
||||||
|
self.code_sum = 0
|
||||||
|
self.code_sqsum = 0
|
||||||
|
self.bits_or = 0 # какие биты хоть раз были 1
|
||||||
|
self.bits_and = 0xFFFFFF # какие биты всегда были 1
|
||||||
|
|
||||||
|
|
||||||
|
def decode_samples(data: bytes):
|
||||||
|
"""150 байт -> 50 знаковых отсчётов."""
|
||||||
|
out = []
|
||||||
|
for i in range(0, len(data), 3):
|
||||||
|
v = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]
|
||||||
|
if v & 0x800000:
|
||||||
|
v -= 0x1000000
|
||||||
|
out.append(v)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def open_port(port, baud):
|
||||||
|
ser = serial.Serial(port, baud, timeout=0.1)
|
||||||
|
try:
|
||||||
|
# Буфер COM-порта в Windows по умолчанию около 4 кБ -- это всего 100 мс
|
||||||
|
# потока, любая заминка в разборе стоит пачки байт
|
||||||
|
ser.set_buffer_size(rx_size=1 << 20, tx_size=1 << 12)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
return ser
|
||||||
|
|
||||||
|
|
||||||
|
def run_dump(port, baud, duration, out_base):
|
||||||
|
"""Только приём в файл, без разбора. Показывает, теряет ли байты сам канал."""
|
||||||
|
raw_path = out_base + ".raw"
|
||||||
|
total = 0
|
||||||
|
slowest = 0.0
|
||||||
|
with open_port(port, baud) as ser, open(raw_path, "wb") as fraw:
|
||||||
|
t0 = time.time()
|
||||||
|
t_end = t0 + duration
|
||||||
|
while time.time() < t_end:
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
chunk = ser.read(4096)
|
||||||
|
slowest = max(slowest, time.perf_counter() - t1)
|
||||||
|
if chunk:
|
||||||
|
fraw.write(chunk)
|
||||||
|
total += len(chunk)
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
|
||||||
|
rate = total / elapsed
|
||||||
|
expect = FRAME_LEN * FS_DEFAULT / SAMPLES_PER_FRAME
|
||||||
|
print(f"принято : {total} байт за {elapsed:.2f} с")
|
||||||
|
print(f"скорость : {rate / 1000:.1f} кБ/с")
|
||||||
|
print(f"ожидалось : {expect / 1000:.1f} кБ/с ({FRAME_LEN} байт/кадр, "
|
||||||
|
f"{FS_DEFAULT / SAMPLES_PER_FRAME:.0f} кадров/с)")
|
||||||
|
print(f"дошло : {100.0 * rate / expect:.1f}%")
|
||||||
|
print(f"дольше всего ждали чтение: {slowest * 1000:.0f} мс")
|
||||||
|
if rate < expect * 0.95:
|
||||||
|
print("\nБайты теряются в канале, разбор тут ни при чём: "
|
||||||
|
"мост, драйвер или скорость порта.")
|
||||||
|
else:
|
||||||
|
print("\nКанал отдаёт весь поток. Значит потери возникают при разборе.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class FrameParser:
|
||||||
|
"""Разбор потока кадров. Единственное место, где описан формат, --
|
||||||
|
им пользуются и запись, и живой просмотр.
|
||||||
|
|
||||||
|
feed() возвращает список (frame_cnt, status, data, gap): data -- 150 байт
|
||||||
|
отсчётов, gap -- сколько кадров потерялось перед этим.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.buf = bytearray()
|
||||||
|
self.expected_cnt = None
|
||||||
|
self.frames_ok = 0
|
||||||
|
self.crc_errors = 0
|
||||||
|
self.resyncs = 0
|
||||||
|
self.padded_frames = 0
|
||||||
|
self.status_flags = 0
|
||||||
|
|
||||||
|
def feed(self, chunk):
|
||||||
|
out = []
|
||||||
|
self.buf += chunk
|
||||||
|
while True:
|
||||||
|
idx = self.buf.find(SYNC)
|
||||||
|
if idx < 0:
|
||||||
|
# Оставляем хвост, в котором SYNC мог разорваться между чтениями
|
||||||
|
if len(self.buf) > 1:
|
||||||
|
del self.buf[:-1]
|
||||||
|
break
|
||||||
|
if idx > 0:
|
||||||
|
self.resyncs += 1
|
||||||
|
del self.buf[:idx]
|
||||||
|
if len(self.buf) < FRAME_LEN:
|
||||||
|
break
|
||||||
|
|
||||||
|
frame = bytes(self.buf[:FRAME_LEN])
|
||||||
|
payload = frame[2:2 + PAYLOAD_LEN]
|
||||||
|
crc_rx = (frame[-2] << 8) | frame[-1]
|
||||||
|
|
||||||
|
if crc16_ccitt(payload) != crc_rx:
|
||||||
|
self.crc_errors += 1
|
||||||
|
# SYNC мог оказаться внутри данных: сдвигаемся и ищем дальше
|
||||||
|
del self.buf[:2]
|
||||||
|
continue
|
||||||
|
|
||||||
|
del self.buf[:FRAME_LEN]
|
||||||
|
|
||||||
|
cnt = (payload[0] << 8) | payload[1]
|
||||||
|
status = payload[2]
|
||||||
|
self.status_flags |= status
|
||||||
|
|
||||||
|
gap = 0
|
||||||
|
if self.expected_cnt is not None and cnt != self.expected_cnt:
|
||||||
|
gap = (cnt - self.expected_cnt) & 0xFFFF
|
||||||
|
self.padded_frames += gap
|
||||||
|
self.expected_cnt = (cnt + 1) & 0xFFFF
|
||||||
|
self.frames_ok += 1
|
||||||
|
out.append((cnt, status, payload[3:], gap))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def run(port, baud, duration, out_base, quiet):
|
||||||
|
raw_path = out_base + ".raw"
|
||||||
|
bin_path = out_base + ".bin"
|
||||||
|
log_path = out_base + ".log"
|
||||||
|
|
||||||
|
st = Stats()
|
||||||
|
log_lines = []
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
log_lines.append(msg)
|
||||||
|
if not quiet:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
log(f"порт {port}, {baud} бод, длительность {duration} с")
|
||||||
|
|
||||||
|
parser = FrameParser()
|
||||||
|
t_end = time.time() + duration
|
||||||
|
gaps_printed = 0
|
||||||
|
|
||||||
|
with open_port(port, baud) as ser, \
|
||||||
|
open(raw_path, "wb") as fraw, open(bin_path, "wb") as fbin:
|
||||||
|
while time.time() < t_end:
|
||||||
|
chunk = ser.read(4096)
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
fraw.write(chunk)
|
||||||
|
|
||||||
|
for cnt, status, data, gap in parser.feed(chunk):
|
||||||
|
if gap:
|
||||||
|
fbin.write(b"\x00" * (gap * SAMPLES_PER_FRAME * 3))
|
||||||
|
st.samples += gap * SAMPLES_PER_FRAME
|
||||||
|
# Печатаем только первые сообщения: вывод в консоль медленный
|
||||||
|
# и на длинной записи сам становится причиной потерь
|
||||||
|
msg = (f"пропуск {gap} кадр(ов) перед FRAME_CNT={cnt}, "
|
||||||
|
f"добито {gap * SAMPLES_PER_FRAME} нулей")
|
||||||
|
log_lines.append(msg)
|
||||||
|
if gaps_printed < 10 and not quiet:
|
||||||
|
print(msg)
|
||||||
|
gaps_printed += 1
|
||||||
|
|
||||||
|
fbin.write(data)
|
||||||
|
st.samples += SAMPLES_PER_FRAME
|
||||||
|
|
||||||
|
for v in decode_samples(data):
|
||||||
|
st.code_sum += v
|
||||||
|
st.code_sqsum += v * v
|
||||||
|
st.bits_or |= v & 0xFFFFFF
|
||||||
|
st.bits_and &= v & 0xFFFFFF
|
||||||
|
if st.code_min is None or v < st.code_min:
|
||||||
|
st.code_min = v
|
||||||
|
if st.code_max is None or v > st.code_max:
|
||||||
|
st.code_max = v
|
||||||
|
|
||||||
|
st.frames_ok = parser.frames_ok
|
||||||
|
st.crc_errors = parser.crc_errors
|
||||||
|
st.resyncs = parser.resyncs
|
||||||
|
st.padded_frames = parser.padded_frames
|
||||||
|
st.status_flags = parser.status_flags
|
||||||
|
|
||||||
|
log("")
|
||||||
|
log(f"кадров принято : {st.frames_ok}")
|
||||||
|
log(f"ошибок CRC : {st.crc_errors}")
|
||||||
|
log(f"ресинхронизаций : {st.resyncs}")
|
||||||
|
log(f"кадров добито нулями: {st.padded_frames}")
|
||||||
|
log(f"отсчётов в .bin : {st.samples} ({st.samples / FS_DEFAULT:.3f} с при {FS_DEFAULT:.0f} Гц)")
|
||||||
|
|
||||||
|
if st.status_flags & 0x01:
|
||||||
|
log("STATUS: было переполнение FIFO в ПЛИС")
|
||||||
|
if st.status_flags & 0x02:
|
||||||
|
log("STATUS: был срыв чтения по DRDY")
|
||||||
|
if st.status_flags == 0 and st.frames_ok:
|
||||||
|
log("STATUS: чисто")
|
||||||
|
|
||||||
|
if st.frames_ok:
|
||||||
|
scale = V_REF / (2 ** 23 - 1)
|
||||||
|
n = st.frames_ok * SAMPLES_PER_FRAME
|
||||||
|
mean = st.code_sum / n
|
||||||
|
var = max(st.code_sqsum / n - mean * mean, 0.0)
|
||||||
|
sigma = var ** 0.5
|
||||||
|
log("")
|
||||||
|
log(f"код min/max/среднее : {st.code_min} / {st.code_max} / {mean:.1f}")
|
||||||
|
log(f"вольт min/max/сред. : {st.code_min * scale:+.6f} / "
|
||||||
|
f"{st.code_max * scale:+.6f} / {mean * scale:+.6f}")
|
||||||
|
log(f"размах : {(st.code_max - st.code_min) * scale:.6f} В "
|
||||||
|
f"(полная шкала {2 * V_REF:.1f} В)")
|
||||||
|
log(f"СКО шума : {sigma:.1f} код ({sigma * scale * 1e6:.2f} мкВ)")
|
||||||
|
|
||||||
|
# Постоянные старшие биты -- норма: при малом размахе сигнала меняются
|
||||||
|
# только младшие. Тревожно обратное: залипшие младшие биты, которые
|
||||||
|
# обязан расшевелить хотя бы собственный шум АЦП.
|
||||||
|
frozen = ((~st.bits_or) | st.bits_and) & 0xFFFFFF
|
||||||
|
low_frozen = frozen & 0x0F
|
||||||
|
if frozen == 0xFFFFFF:
|
||||||
|
log("биты : НИ ОДИН из 24 не менялся — тракт отдаёт константу")
|
||||||
|
elif low_frozen:
|
||||||
|
log("биты : ВНИМАНИЕ, не менялись младшие " + ", ".join(
|
||||||
|
str(i) for i in range(3, -1, -1) if low_frozen >> i & 1) +
|
||||||
|
" — похоже на сбой чтения, а не на сигнал")
|
||||||
|
else:
|
||||||
|
top = 0
|
||||||
|
for i in range(23, -1, -1):
|
||||||
|
if frozen >> i & 1:
|
||||||
|
top += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if top:
|
||||||
|
log(f"биты : младшие {24 - top} переключались, "
|
||||||
|
f"старшие {top} постоянны — норма при малом размахе")
|
||||||
|
else:
|
||||||
|
log("биты : все 24 переключались")
|
||||||
|
|
||||||
|
with open(log_path, "w", encoding="utf-8") as flog:
|
||||||
|
flog.write("\n".join(log_lines) + "\n")
|
||||||
|
|
||||||
|
return 1 if (st.crc_errors or st.status_flags or not st.frames_ok) else 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--port", help="например COM5")
|
||||||
|
ap.add_argument("--baud", type=int, default=921600)
|
||||||
|
ap.add_argument("--seconds", type=float, default=10.0)
|
||||||
|
ap.add_argument("--out", default="capture", help="базовое имя файлов без расширения")
|
||||||
|
ap.add_argument("--quiet", action="store_true")
|
||||||
|
ap.add_argument("--list", action="store_true", help="показать доступные порты")
|
||||||
|
ap.add_argument("--dump", action="store_true",
|
||||||
|
help="только приём в .raw без разбора: проверка канала")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
for p in list_ports.comports():
|
||||||
|
print(f"{p.device} {p.description}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not args.port:
|
||||||
|
ap.error("нужен --port (список портов: --list)")
|
||||||
|
|
||||||
|
if args.dump:
|
||||||
|
return run_dump(args.port, args.baud, args.seconds, args.out)
|
||||||
|
|
||||||
|
return run(args.port, args.baud, args.seconds, args.out, args.quiet)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Живой просмотр потока с AD7766: осциллограмма, спектр и счётчики.
|
||||||
|
|
||||||
|
Нужен, чтобы глазами проконтролировать сигнал до того, как писать запись:
|
||||||
|
видно и форму, и шум, и наводки, и целостность кадров.
|
||||||
|
|
||||||
|
Разбор кадров общий с uart_capture.py -- формат описан там в одном месте.
|
||||||
|
Запись сюда встроена не для удобства, а по необходимости: COM-порт держит
|
||||||
|
один процесс, и параллельно запустить uart_capture.py на том же порту нельзя.
|
||||||
|
|
||||||
|
Управление -- кнопками внизу окна, те же действия продублированы клавишами:
|
||||||
|
r запись d убрать постоянную составляющую
|
||||||
|
a автомасштаб s спектр: плотность или амплитуда
|
||||||
|
f пауза q выход
|
||||||
|
|
||||||
|
Зум и панорама -- штатной панелью инструментов matplotlib. Автомасштаб при
|
||||||
|
этом выключается сам, иначе он затирал бы выбранный масштаб на каждом кадре.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("TkAgg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.widgets import Button
|
||||||
|
|
||||||
|
import uart_capture as uc
|
||||||
|
|
||||||
|
VREF = 5.0
|
||||||
|
LSB = 2 * VREF / 2 ** 24 # 596.0 нВ
|
||||||
|
NFFT = 8192
|
||||||
|
|
||||||
|
|
||||||
|
def decode(data: bytes) -> np.ndarray:
|
||||||
|
"""150 байт кадра -> 50 знаковых отсчётов."""
|
||||||
|
a = np.frombuffer(data, dtype=np.uint8).reshape(-1, 3).astype(np.int32)
|
||||||
|
v = (a[:, 0] << 16) | (a[:, 1] << 8) | a[:, 2]
|
||||||
|
return np.where(v & 0x800000, v - 0x1000000, v)
|
||||||
|
|
||||||
|
|
||||||
|
def minmax_decimate(y, buckets):
|
||||||
|
"""Прореживание для отрисовки с сохранением огибающей: на каждый столбец
|
||||||
|
пикселей берём минимум и максимум, иначе выбросы шума пропадут."""
|
||||||
|
n = len(y)
|
||||||
|
if n <= buckets * 2:
|
||||||
|
return np.arange(n), y
|
||||||
|
step = n // buckets
|
||||||
|
blocks = y[:step * buckets].reshape(buckets, step)
|
||||||
|
idx = np.arange(buckets) * step
|
||||||
|
x = np.empty(buckets * 2)
|
||||||
|
v = np.empty(buckets * 2)
|
||||||
|
x[0::2], x[1::2] = idx, idx + step - 1
|
||||||
|
v[0::2], v[1::2] = blocks.min(axis=1), blocks.max(axis=1)
|
||||||
|
return x, v
|
||||||
|
|
||||||
|
|
||||||
|
class Reader(threading.Thread):
|
||||||
|
"""Читает порт, разбирает кадры, копит отсчёты для отрисовки."""
|
||||||
|
|
||||||
|
daemon = True
|
||||||
|
|
||||||
|
def __init__(self, port, baud, depth):
|
||||||
|
super().__init__()
|
||||||
|
self.port, self.baud = port, baud
|
||||||
|
self.samples = collections.deque(maxlen=depth)
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.stop_evt = threading.Event()
|
||||||
|
self.parser = uc.FrameParser()
|
||||||
|
self.err = None
|
||||||
|
|
||||||
|
self.bytes_total = 0
|
||||||
|
self.t0 = None
|
||||||
|
self.rec_path = None # запрос от интерфейса: путь или None
|
||||||
|
self.rec_active = None # что пишется сейчас
|
||||||
|
self.rec_samples = 0
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
ser = uc.open_port(self.port, self.baud)
|
||||||
|
except Exception as e:
|
||||||
|
self.err = str(e)
|
||||||
|
return
|
||||||
|
self.t0 = time.time()
|
||||||
|
fraw = fbin = None
|
||||||
|
try:
|
||||||
|
while not self.stop_evt.is_set():
|
||||||
|
# Файлы открывает и закрывает только тот поток, что в них пишет
|
||||||
|
if self.rec_path != self.rec_active:
|
||||||
|
if fraw:
|
||||||
|
fraw.close(); fbin.close()
|
||||||
|
fraw = fbin = None
|
||||||
|
if self.rec_path:
|
||||||
|
fraw = open(self.rec_path + ".raw", "wb")
|
||||||
|
fbin = open(self.rec_path + ".bin", "wb")
|
||||||
|
self.rec_samples = 0
|
||||||
|
self.rec_active = self.rec_path
|
||||||
|
|
||||||
|
chunk = ser.read(4096)
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
self.bytes_total += len(chunk)
|
||||||
|
if fraw:
|
||||||
|
fraw.write(chunk)
|
||||||
|
|
||||||
|
for _cnt, _status, data, gap in self.parser.feed(chunk):
|
||||||
|
if fbin:
|
||||||
|
if gap:
|
||||||
|
fbin.write(b"\x00" * (gap * uc.SAMPLES_PER_FRAME * 3))
|
||||||
|
self.rec_samples += gap * uc.SAMPLES_PER_FRAME
|
||||||
|
fbin.write(data)
|
||||||
|
self.rec_samples += uc.SAMPLES_PER_FRAME
|
||||||
|
with self.lock:
|
||||||
|
self.samples.extend(decode(data))
|
||||||
|
except Exception as e:
|
||||||
|
self.err = str(e)
|
||||||
|
finally:
|
||||||
|
if fraw:
|
||||||
|
fraw.close(); fbin.close()
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
def snapshot(self):
|
||||||
|
with self.lock:
|
||||||
|
return np.fromiter(self.samples, dtype=np.int32, count=len(self.samples))
|
||||||
|
|
||||||
|
|
||||||
|
class Live:
|
||||||
|
def __init__(self, reader, fs, window_s, out_base):
|
||||||
|
self.rd = reader
|
||||||
|
self.fs = fs
|
||||||
|
self.out_base = out_base
|
||||||
|
self.window_s = window_s
|
||||||
|
self.sub_dc = False
|
||||||
|
self.frozen = False
|
||||||
|
self.psd_mode = True
|
||||||
|
self.autoscale = True
|
||||||
|
self.rec_n = 0
|
||||||
|
|
||||||
|
self.fig = plt.figure(figsize=(11, 8))
|
||||||
|
self.fig.canvas.manager.set_window_title("AD7766 -- живой просмотр")
|
||||||
|
self.ax_t = self.fig.add_axes([0.08, 0.50, 0.89, 0.33])
|
||||||
|
self.ax_f = self.fig.add_axes([0.08, 0.14, 0.89, 0.27])
|
||||||
|
|
||||||
|
self.ln_t, = self.ax_t.plot([], [], lw=0.8)
|
||||||
|
self.ax_t.set_xlabel("время, с")
|
||||||
|
self.ax_t.set_ylabel("напряжение, В")
|
||||||
|
self.ax_t.grid(alpha=0.3)
|
||||||
|
self.ax_t.set_xlim(-window_s, 0)
|
||||||
|
|
||||||
|
self.ln_f, = self.ax_f.plot([], [], lw=0.8)
|
||||||
|
self.ax_f.set_xlabel("частота, Гц")
|
||||||
|
self.ax_f.set_ylabel("нВ/√Гц")
|
||||||
|
self.ax_f.grid(alpha=0.3, which="both")
|
||||||
|
self.ax_f.set_xscale("log")
|
||||||
|
self.ax_f.set_yscale("log")
|
||||||
|
# Пределы обеих осей задаём сразу положительными: до прихода данных
|
||||||
|
# линия пуста, а логарифмическая шкала с нулём в пределах падает
|
||||||
|
self.ax_f.set_xlim(1, fs / 2)
|
||||||
|
self.ax_f.set_ylim(1, 1e6)
|
||||||
|
|
||||||
|
self.txt = self.fig.text(0.01, 0.985, "", family="monospace",
|
||||||
|
fontsize=9, va="top")
|
||||||
|
|
||||||
|
self.buttons = []
|
||||||
|
self.b_rec = self._button(0.08, "● Запись", self.toggle_rec)
|
||||||
|
self.b_auto = self._button(0.24, "Автомасштаб: вкл", self.toggle_auto)
|
||||||
|
self.b_dc = self._button(0.40, "Как есть", self.toggle_dc)
|
||||||
|
self.b_sp = self._button(0.56, "Спектр: плотность", self.toggle_sp)
|
||||||
|
self.b_frz = self._button(0.72, "Пауза", self.toggle_frz)
|
||||||
|
|
||||||
|
self.fig.canvas.mpl_connect("key_press_event", self.on_key)
|
||||||
|
|
||||||
|
def _button(self, x, label, cb):
|
||||||
|
ax = self.fig.add_axes([x, 0.025, 0.15, 0.05])
|
||||||
|
b = Button(ax, label)
|
||||||
|
b.on_clicked(cb)
|
||||||
|
self.buttons.append(b)
|
||||||
|
return b
|
||||||
|
|
||||||
|
# --- переключатели ---
|
||||||
|
|
||||||
|
def toggle_rec(self, _=None):
|
||||||
|
if self.rd.rec_path:
|
||||||
|
self.rd.rec_path = None
|
||||||
|
self.b_rec.label.set_text("● Запись")
|
||||||
|
self.b_rec.ax.set_facecolor("0.85")
|
||||||
|
else:
|
||||||
|
self.rec_n += 1
|
||||||
|
self.rd.rec_path = f"{self.out_base}_{self.rec_n:02d}"
|
||||||
|
self.b_rec.label.set_text("■ Стоп")
|
||||||
|
self.b_rec.ax.set_facecolor("#ff9c9c")
|
||||||
|
|
||||||
|
def toggle_auto(self, _=None):
|
||||||
|
self.autoscale = not self.autoscale
|
||||||
|
self.b_auto.label.set_text(
|
||||||
|
"Автомасштаб: вкл" if self.autoscale else "Автомасштаб: выкл")
|
||||||
|
|
||||||
|
def toggle_dc(self, _=None):
|
||||||
|
self.sub_dc = not self.sub_dc
|
||||||
|
self.b_dc.label.set_text("Без постоянной" if self.sub_dc else "Как есть")
|
||||||
|
|
||||||
|
def toggle_sp(self, _=None):
|
||||||
|
self.psd_mode = not self.psd_mode
|
||||||
|
self.b_sp.label.set_text(
|
||||||
|
"Спектр: плотность" if self.psd_mode else "Спектр: амплитуда")
|
||||||
|
|
||||||
|
def toggle_frz(self, _=None):
|
||||||
|
self.frozen = not self.frozen
|
||||||
|
self.b_frz.label.set_text("Продолжить" if self.frozen else "Пауза")
|
||||||
|
|
||||||
|
def on_key(self, ev):
|
||||||
|
{"q": lambda: plt.close(self.fig), "r": self.toggle_rec,
|
||||||
|
"a": self.toggle_auto, "d": self.toggle_dc,
|
||||||
|
"s": self.toggle_sp, "f": self.toggle_frz}.get(ev.key, lambda: None)()
|
||||||
|
|
||||||
|
def _toolbar_busy(self):
|
||||||
|
"""Пользователь работает лупой или рукой -- пределы осей не трогаем."""
|
||||||
|
tb = getattr(self.fig.canvas, "toolbar", None)
|
||||||
|
return bool(getattr(tb, "mode", "")) if tb else False
|
||||||
|
|
||||||
|
# --- отрисовка ---
|
||||||
|
|
||||||
|
def update(self, _=None):
|
||||||
|
if self._toolbar_busy() and self.autoscale:
|
||||||
|
self.toggle_auto()
|
||||||
|
if self.frozen:
|
||||||
|
return self.ln_t, self.ln_f
|
||||||
|
|
||||||
|
y = self.rd.snapshot()
|
||||||
|
if len(y) < NFFT:
|
||||||
|
self.txt.set_text(f"ждём данные... принято {len(y)} отсчётов"
|
||||||
|
+ (f"\nОШИБКА: {self.rd.err}" if self.rd.err else ""))
|
||||||
|
return self.ln_t, self.ln_f
|
||||||
|
|
||||||
|
v = y * LSB
|
||||||
|
mean = v.mean()
|
||||||
|
vv = v - mean if self.sub_dc else v
|
||||||
|
|
||||||
|
x, d = minmax_decimate(vv, 1200)
|
||||||
|
self.ln_t.set_data((x - len(vv)) / self.fs, d)
|
||||||
|
|
||||||
|
seg = v[-NFFT:] - v[-NFFT:].mean()
|
||||||
|
w = np.hanning(NFFT)
|
||||||
|
sp = np.abs(np.fft.rfft(seg * w)) ** 2
|
||||||
|
f = np.fft.rfftfreq(NFFT, 1 / self.fs)
|
||||||
|
# Ноль на логарифмической оси недопустим, поэтому подрезаем снизу
|
||||||
|
# заведомо ничтожным уровнем
|
||||||
|
if self.psd_mode:
|
||||||
|
psd = sp / ((w ** 2).sum() * self.fs) * 2
|
||||||
|
self.ln_f.set_data(f[1:], np.maximum(np.sqrt(psd[1:]) * 1e9, 1e-3))
|
||||||
|
self.ax_f.set_ylabel("нВ/√Гц")
|
||||||
|
else:
|
||||||
|
amp = np.sqrt(sp[1:]) / w.sum() * 2 * 1e6
|
||||||
|
self.ln_f.set_data(f[1:], np.maximum(amp, 1e-6))
|
||||||
|
self.ax_f.set_ylabel("амплитуда, мкВ")
|
||||||
|
|
||||||
|
if self.autoscale:
|
||||||
|
lo, hi = d.min(), d.max()
|
||||||
|
pad = max((hi - lo) * 0.1, 1e-7)
|
||||||
|
self.ax_t.set_ylim(lo - pad, hi + pad)
|
||||||
|
self.ax_t.set_xlim(-len(vv) / self.fs, 0)
|
||||||
|
|
||||||
|
# Пределы спектра считаем сами. relim() с autoscale_view() здесь
|
||||||
|
# не годятся: лупа снимает у оси внутренний флаг autoscale_on,
|
||||||
|
# после чего эта пара молча перестаёт работать навсегда.
|
||||||
|
yd = np.asarray(self.ln_f.get_ydata())
|
||||||
|
pos = yd[yd > 0]
|
||||||
|
if len(pos):
|
||||||
|
top = pos.max() * 3
|
||||||
|
bot = max(np.percentile(pos, 1) / 3, top / 1e6)
|
||||||
|
self.ax_f.set_ylim(bot, top)
|
||||||
|
self.ax_f.set_xlim(1, self.fs / 2)
|
||||||
|
|
||||||
|
p = self.rd.parser
|
||||||
|
el = max(time.time() - (self.rd.t0 or time.time()), 1e-3)
|
||||||
|
rec = (f"ЗАПИСЬ {self.rd.rec_active} {self.rd.rec_samples} отсч."
|
||||||
|
if self.rd.rec_active else "не пишем")
|
||||||
|
st = ("чисто" if p.status_flags == 0 else
|
||||||
|
f"FIFO={p.status_flags & 1} DRDY={(p.status_flags >> 1) & 1}")
|
||||||
|
self.txt.set_text(
|
||||||
|
f"среднее {mean * 1e3:+9.3f} мВ СКО {v.std() / LSB:7.1f} код "
|
||||||
|
f"({v.std() * 1e6:8.2f} мкВ) размах {(v.max() - v.min()) * 1e6:9.1f} мкВ\n"
|
||||||
|
f"кадров {p.frames_ok:<8d} CRC {p.crc_errors:<5d} потеряно {p.padded_frames:<6d} "
|
||||||
|
f"ресинхр {p.resyncs:<5d} STATUS {st}\n"
|
||||||
|
f"канал {self.rd.bytes_total / el / 1000:5.1f} кБ/с "
|
||||||
|
f"кадров/с {p.frames_ok / el:5.1f} окно {len(vv) / self.fs:.2f} с "
|
||||||
|
f"автомасштаб {'вкл' if self.autoscale else 'ВЫКЛ'} {rec}")
|
||||||
|
return self.ln_t, self.ln_f
|
||||||
|
|
||||||
|
|
||||||
|
def selftest():
|
||||||
|
"""Прогон конвейера и всех веток отрисовки без порта и без окна."""
|
||||||
|
import struct
|
||||||
|
import types
|
||||||
|
matplotlib.use("Agg", force=True)
|
||||||
|
|
||||||
|
parser = uc.FrameParser()
|
||||||
|
samples = []
|
||||||
|
for k in range(400):
|
||||||
|
s = [int(8000 * np.sin(2 * np.pi * 300 * (k * 50 + i) / 12500))
|
||||||
|
for i in range(uc.SAMPLES_PER_FRAME)]
|
||||||
|
data = b"".join(struct.pack(">i", v & 0xFFFFFF)[1:] for v in s)
|
||||||
|
payload = struct.pack(">HB", k, 0) + data
|
||||||
|
frame = uc.SYNC + payload + struct.pack(">H", uc.crc16_ccitt(payload))
|
||||||
|
for _c, _st, d, _g in parser.feed(frame):
|
||||||
|
samples.extend(decode(d))
|
||||||
|
y = np.asarray(samples, dtype=np.int32)
|
||||||
|
assert parser.frames_ok == 400 and parser.crc_errors == 0
|
||||||
|
assert len(y) == 400 * uc.SAMPLES_PER_FRAME
|
||||||
|
|
||||||
|
xs, dd = minmax_decimate(y, 500)
|
||||||
|
assert len(xs) == 1000 and dd.max() == y.max() and dd.min() == y.min()
|
||||||
|
sp = np.abs(np.fft.rfft((y[:NFFT] - y[:NFFT].mean()) * np.hanning(NFFT)))
|
||||||
|
peak = np.fft.rfftfreq(NFFT, 1 / 12500.0)[np.argmax(sp)]
|
||||||
|
assert abs(peak - 300) < 3, peak
|
||||||
|
print(f"конвейер: 400 кадров, {len(y)} отсчётов, пик спектра {peak:.1f} Гц")
|
||||||
|
|
||||||
|
rd = types.SimpleNamespace(
|
||||||
|
parser=parser, bytes_total=390000, t0=time.time(), rec_path=None,
|
||||||
|
rec_active=None, rec_samples=0, err=None,
|
||||||
|
snapshot=lambda: np.zeros(10, dtype=np.int32))
|
||||||
|
live = Live(rd, 12500.0, 2.0, "live")
|
||||||
|
|
||||||
|
# Рисуем именно холст, а не только считаем: логарифмическая шкала падает
|
||||||
|
# на отрисовке, а не на подготовке данных. Пустое состояние -- первым.
|
||||||
|
live.update()
|
||||||
|
live.fig.canvas.draw()
|
||||||
|
print(" отрисовка без данных -- ок")
|
||||||
|
|
||||||
|
rd.snapshot = lambda: y
|
||||||
|
live.update()
|
||||||
|
live.fig.canvas.draw()
|
||||||
|
print(" отрисовка с данными -- ок")
|
||||||
|
lim0 = live.ax_t.get_ylim()
|
||||||
|
for name, fn in (("автомасштаб", live.toggle_auto), ("постоянная", live.toggle_dc),
|
||||||
|
("спектр", live.toggle_sp), ("пауза", live.toggle_frz)):
|
||||||
|
fn()
|
||||||
|
live.update()
|
||||||
|
live.fig.canvas.draw()
|
||||||
|
print(f" переключение: {name} -- ок")
|
||||||
|
live.toggle_frz()
|
||||||
|
|
||||||
|
# С выключенным автомасштабом пределы обязаны остаться нашими
|
||||||
|
live.ax_t.set_ylim(-1.0, 1.0)
|
||||||
|
live.update()
|
||||||
|
assert live.ax_t.get_ylim() == (-1.0, 1.0), "автомасштаб затирает зум"
|
||||||
|
print(" зум при выключенном автомасштабе сохраняется -- ок")
|
||||||
|
|
||||||
|
live.toggle_auto()
|
||||||
|
live.update()
|
||||||
|
assert live.ax_t.get_ylim() != (-1.0, 1.0)
|
||||||
|
assert live.ax_t.get_ylim() == lim0
|
||||||
|
print(" автомасштаб обратно включается -- ок")
|
||||||
|
|
||||||
|
# Спектр обязан следовать за уровнем сигнала
|
||||||
|
f_small = live.ax_f.get_ylim()
|
||||||
|
rd.snapshot = lambda: y * 100
|
||||||
|
live.update()
|
||||||
|
f_big = live.ax_f.get_ylim()
|
||||||
|
assert f_big[1] > f_small[1] * 10, (f_small, f_big)
|
||||||
|
print(" спектр масштабируется за сигналом -- ок")
|
||||||
|
|
||||||
|
# Лупа снимает у оси внутренний флаг autoscale_on -- после этого
|
||||||
|
# масштабирование обязано продолжать работать
|
||||||
|
live.ax_f.set_autoscaley_on(False)
|
||||||
|
live.ax_t.set_autoscaley_on(False)
|
||||||
|
rd.snapshot = lambda: y
|
||||||
|
live.update()
|
||||||
|
assert live.ax_f.get_ylim()[1] < f_big[1] / 10, live.ax_f.get_ylim()
|
||||||
|
assert live.ax_t.get_ylim() == lim0
|
||||||
|
print(" масштабирование переживает работу лупой -- ок")
|
||||||
|
|
||||||
|
live.toggle_rec()
|
||||||
|
assert rd.rec_path == "live_01" and "Стоп" in live.b_rec.label.get_text()
|
||||||
|
live.toggle_rec()
|
||||||
|
assert rd.rec_path is None and "Запись" in live.b_rec.label.get_text()
|
||||||
|
print(" кнопка записи -- ок")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--port")
|
||||||
|
ap.add_argument("--baud", type=int, default=921600)
|
||||||
|
ap.add_argument("--fs", type=float, default=uc.FS_DEFAULT)
|
||||||
|
ap.add_argument("--window", type=float, default=2.0, help="окно осциллограммы, с")
|
||||||
|
ap.add_argument("--out", default="live", help="базовое имя для записей")
|
||||||
|
ap.add_argument("--selftest", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.selftest:
|
||||||
|
return selftest()
|
||||||
|
if not args.port:
|
||||||
|
ap.error("нужен --port (список портов: uart_capture.py --list)")
|
||||||
|
|
||||||
|
rd = Reader(args.port, args.baud, int(args.fs * max(args.window, 1.0)))
|
||||||
|
rd.start()
|
||||||
|
|
||||||
|
live = Live(rd, args.fs, args.window, args.out)
|
||||||
|
from matplotlib.animation import FuncAnimation
|
||||||
|
# Ссылку держим на объекте: локальную переменную сборщик может забрать
|
||||||
|
live.anim = FuncAnimation(live.fig, live.update, interval=100,
|
||||||
|
blit=False, cache_frame_data=False)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
rd.stop_evt.set()
|
||||||
|
rd.join(timeout=2)
|
||||||
|
if rd.err:
|
||||||
|
print(f"ошибка чтения порта: {rd.err}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user