В статье рассматривается процесс создания программы для инициализации и управления микросхемой ad9363. Описанный подход применим для всего семейства ad936x. Всё реализовано под клон ADALM-Pluto с помощью Vivado 2021 и Vitis 2021 на Си.
Я не эксперт в области программирования ad936x. Это та область, которую я хочу изучить. Я пытаюсь разобраться в основах и мне проще учиться, когда я вижу, как это работает. Вот почему я это делаю. Не потому, что я уже всё знаю об ad936x. И если Вы тоже чему-то научитесь, прочитав мою статью, это будет здорово. Но пожалуйста, не полагайтесь на мои статьи, чтобы понять теорию. Есть множество отличных книг и других источников по этой теме, где Вы сможете найти более подробную информацию. Я надеюсь, что благодаря этой статье Вы научитесь применять некоторые базовые принципы программирования ad936x в реальных системах. Если Вы заметите ошибку или что-то, что можно улучшить, пожалуйста, напишите мне в комментариях. Тогда мы сможем учиться вместе, и, возможно, я смогу исправить это и показать в новых статьях.

Итак, перед нами клон достаточно популярного SDR под названием ADALM-PLUTO и нам необходимо написать для него свою прошивку. Этот клон отличается от оригинальной платы тем, что использована немного другая ПЛИС — XC7Z010-CLG400. Что это может значить для разработчика прошивки? А то, что придётся связаться с продавцом и попросить схему. В этом случае повезло, и продавец поделился файлом .xdc.

Путей для создания прошивок для ad936x и PlutoSDR, в частности, много. Здесь предлагается пройти по одному из самых простых. Есть такой ресурс https://wiki.analog.com. Он очень может помочь при разработке подобного софта. Например, есть страница https://wiki.analog.com/resources/fpga/docs/build. Давайте пройдёмся по этим шагам.

В этой статье разработка будет вестись на системе Windows, поэтому в CYGWIN выполним команду
export PATH=$PATH:/cygdrive/C/Xilinx/Vivado/2021.1/bin
Обратите внимание, что path_to заменён на C и указана версия Vivado. После выполнения команды which vivado возвращает правильный путь до Vivado.

Теперь можно клонировать репозиторий, и переключиться на ветку hdl_2021_r1, потому что в этой статье разработка ведётся с помощью Vivado 2021.1.

Мы используем клон с другой ПЛИС, поэтому необходимо подправить исходники. Первым делом заменим .xdc на предоставленный.


На двух изображениях выше видно, что назначения различаются. Например, rx_clk_in был на L12, а стал на U18. В файле system_bd.tcl на 41 строке есть параметр, значение которого необходимо заменить в соответствии с используемой на плате клона ПЛИС.

В файле system_project.tcl на 6 строке необходимо заменить модель ПЛИС.

Это все изменения, которые касаются железа в части сборки HDL дизайна.

Теперь на странице WIKI предлагается перейти в необходимый проект и выполнить команду make. Обратите внимание, что утилита make должна быть установлена в Cygwin и на команду which make возвращать правильный путь, например,
$ which make /usr/bin/make

Процесс этот может быть долгим. Относительно, например, мой старенький ноутбук 2018 года, который тогда был с железом выше среднего, теперь делает это около 20 минут, а PC с Ryzen9 со старой работы собирал проект fmcomms2 под kc705 или zc706 за 10 минут. Кстати, процесс выполнения make тогда внезапно и по неизвестной причине завершался с error, тогда make можно просто запустить снова и всё хорошо собиралось дальше.

Утилита завершила свою работу, и теперь можно открыть проект с помощью Vivado.
Проект действительно собран под необходимую ПЛИС.

И новые назначения используются в соответствии с предоставленным файлом .xdc.

На этом этапе можно устроить промежуточную проверку и собрать простое приложение Hello World, которое выведет информацию через UART. Таким способом можно убедиться, что HDL дизайн подходит для этой платы. Для приложения Vitis необходимо экспортировать hardware с помощью File->Export->Export Hardware... Тогда в указанной директории появится необходимый файл .xsa

Обратите внимание на опцию Include bitstream, это важно.

Теперь можно открыть Vitis. Создать новое приложение с помощью File->New->Application Project...
На следующих этапах многие вещи понятны интуитивно. Главное — необходимо выбрать созданный .xsa файл:




Когда приложение будет готово, перед нами встанет вопрос о том, как загрузить прошивку в плату. На wiki.analog есть страничка https://wiki.analog.com/university/tools/pluto/devs/fpga, но там написано что-то невнятное. В любом случае на плате есть отверстия под разъём FTSH-105-01-L-D. Это оказался достаточно редкий разъём, и в продаже его найти не удалось. Описание контактов есть на схеме, которая доступна на https://wiki.analog.com/university/tools/pluto/hacking/hardware. Там несколько ревизий, но JTAG везде одинаковый. Кстати, для программирования Xilinx можно использовать сравнительно дешёвый программатор от Waveshare.



Подключаем программатор, потом крайний microUSB PlutoSDR, при этом STATUS светодиод на программаторе засветится зелёным, на плате засветится крайний светодиод, а в диспетчере устройств появится новый COM-порт. Необходимо подключиться любым удобным терминалом к этому COM-порту, чтобы увидеть UART вывод запрограммированной платы.

Когда всё подключено, необходимо вернуться к Vitis и войти в меню Run Configurations.
Два раза кликаем на System Project Debug и запускаем Run.

Тогда начнётся загрузка прошивки, а когда это будет сделано, то в терминале появится сообщение.

Подводя промежуточный итог, имеется рабочий HDL дизайн, который можно использовать для создания приложений и их успешного выполнения. Приступим к созданию приложения для инициализации и управления работой микросхемы ad936x.
Путей создания программы для микропроцессора, который будет управлять работой микросхемы, как и в случае с созданием HDL дизайна, несколько. Даже микропроцессоров «из коробки» доступно как минимум два. Это MicroBlaze и Zynq ARM ядра. В ADALM-PLUTO используется Zynq, как и в клоне, описываемом в этой статье. Обратимся к странице https://wiki.analog.com/resources/no-os/build. Откроем GIT BASH от имени администратора и пропишем путь до VItis с помощью команды,
$ export PATH=$PATH:"/c/Xilinx/Vitis/2021.1/bin"
Теперь команда which должна возвращать путь,
$ which vitis /c/Xilinx/Vitis/2021.1/bin/vitis
Потребуется компилятор. На момент написания статьи получилось зайти и скачать последнюю доступную версию по ссылке. Если устанавливалось по пути по умолчанию, то прописать путь можно командой
$ export PATH=$PATH:"/c/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin"
Для проверки можно посмотреть версию
$ arm-none-eabi-gcc --version
Репозиторий no-OS надо клонировать рекурсивно
git clone --recursive https://github.com/analogdevicesinc/no-OS
У меня это заняло сравнительно значительное время. Не забудьте переключиться на ветку 2021_R1 или необходимую Вам. После этого можно скопировать созданный ранее .xsa файл в корневой каталог проекта ad9361, причём .xsa файл лучше сразу переименовать в system_top.xsa, чтобы потом не словить ошибку из-за особенностей готовых makefile. Файл app_config.h необходимо отредактировать. На 47 строке есть define, который выберет используемую нами микросхему. Но если Вам повезёт, то можно оставить define на 45 строке. Об этом рассказывается на странице https://wiki.analog.com/university/tools/pluto/hacking/hardware. В этом эксперименте останется define для ad9361, несмотря на то, что припаяна микросхема ad9363. Таким образом, этот исходный код годится для всего семейства ad936x.
У меня уже получилось с помощью этих исходников запустить и ad9361, и ad9364, и теперь ad9363. Необходимо только менять настройки под конкретную микросхему, например, ad9364 имеет по одному каналу на приём и передачу. Это работает.
После идёт блок define, которые отвечают за определённые примеры. Для проверки нашей работы мы будем анализировать не только debug информацию, которая печатается в терминале. Мы включим рядом передатчик, который транслирует в эфир синусоиду, примем этот сигнал на Pluto с bare-metal приложением, напечатаем в терминал отсчёты с ацп и построим из этих отсчётов график. Для этого раскомментируем строки 55 с ADC_DMA_EXAMPLE , 57 с DAC_DMA_EXAMPLE и 59 с TDD_SWITCH_STATE_EXAMPLE. Дальше переходим к файлу main.c. В этом файле нас больше всего пока интересует структура default_init_param. На строке 186 отключим режим FDD (0, //frequency_division_duplex_mode_enable *** adi,frequency-division-duplex-mode-enable). На строке 223 включим тактирование от внешнего опорного источника тактового сигнала (1, //xo_disable_use_ext_refclk_enable *** adi,xo-disable-use-ext-refclk-enable). На строке 352 поменяем местами порты 0 и 1 (1, //swap_ports_enable *** adi,swap-ports-enable). На строке 354 отключим режим LVDS (0, //lvds_mode_enable *** adi,lvds-mode-enable). На строке 357 включим опцию full_port_enable (1, //full_port_enable *** adi,full-port-enable).
Вся структура целиком
AD9361_InitParam default_init_param = { /* Device selection */ ID_AD9361, // dev_sel /* Reference Clock */ 40000000UL, //reference_clk_rate /* Base Configuration */ 1, //two_rx_two_tx_mode_enable *** adi,2rx-2tx-mode-enable 1, //one_rx_one_tx_mode_use_rx_num *** adi,1rx-1tx-mode-use-rx-num 1, //one_rx_one_tx_mode_use_tx_num *** adi,1rx-1tx-mode-use-tx-num 0, //frequency_division_duplex_mode_enable *** adi,frequency-division-duplex-mode-enable 0, //frequency_division_duplex_independent_mode_enable *** adi,frequency-division-duplex-independent-mode-enable 0, //tdd_use_dual_synth_mode_enable *** adi,tdd-use-dual-synth-mode-enable 0, //tdd_skip_vco_cal_enable *** adi,tdd-skip-vco-cal-enable 0, //tx_fastlock_delay_ns *** adi,tx-fastlock-delay-ns 0, //rx_fastlock_delay_ns *** adi,rx-fastlock-delay-ns 0, //rx_fastlock_pincontrol_enable *** adi,rx-fastlock-pincontrol-enable 0, //tx_fastlock_pincontrol_enable *** adi,tx-fastlock-pincontrol-enable 0, //external_rx_lo_enable *** adi,external-rx-lo-enable 0, //external_tx_lo_enable *** adi,external-tx-lo-enable 5, //dc_offset_tracking_update_event_mask *** adi,dc-offset-tracking-update-event-mask 6, //dc_offset_attenuation_high_range *** adi,dc-offset-attenuation-high-range 5, //dc_offset_attenuation_low_range *** adi,dc-offset-attenuation-low-range 0x28, //dc_offset_count_high_range *** adi,dc-offset-count-high-range 0x32, //dc_offset_count_low_range *** adi,dc-offset-count-low-range 0, //split_gain_table_mode_enable *** adi,split-gain-table-mode-enable MAX_SYNTH_FREF, //trx_synthesizer_target_fref_overwrite_hz *** adi,trx-synthesizer-target-fref-overwrite-hz 0, // qec_tracking_slow_mode_enable *** adi,qec-tracking-slow-mode-enable /* ENSM Control */ 0, //ensm_enable_pin_pulse_mode_enable *** adi,ensm-enable-pin-pulse-mode-enable 0, //ensm_enable_txnrx_control_enable *** adi,ensm-enable-txnrx-control-enable /* LO Control */ 2400000000UL, //rx_synthesizer_frequency_hz *** adi,rx-synthesizer-frequency-hz 2400000000UL, //tx_synthesizer_frequency_hz *** adi,tx-synthesizer-frequency-hz 1, //tx_lo_powerdown_managed_enable *** adi,tx-lo-powerdown-managed-enable /* Rate & BW Control */ {983040000, 245760000, 122880000, 61440000, 30720000, 30720000},// rx_path_clock_frequencies[6] *** adi,rx-path-clock-frequencies {983040000, 122880000, 122880000, 61440000, 30720000, 30720000},// tx_path_clock_frequencies[6] *** adi,tx-path-clock-frequencies 18000000,//rf_rx_bandwidth_hz *** adi,rf-rx-bandwidth-hz 18000000,//rf_tx_bandwidth_hz *** adi,rf-tx-bandwidth-hz /* RF Port Control */ 0, //rx_rf_port_input_select *** adi,rx-rf-port-input-select 0, //tx_rf_port_input_select *** adi,tx-rf-port-input-select /* TX Attenuation Control */ 10000, //tx_attenuation_mdB *** adi,tx-attenuation-mdB 0, //update_tx_gain_in_alert_enable *** adi,update-tx-gain-in-alert-enable /* Reference Clock Control */ 1, //xo_disable_use_ext_refclk_enable *** adi,xo-disable-use-ext-refclk-enable {8, 5920}, //dcxo_coarse_and_fine_tune[2] *** adi,dcxo-coarse-and-fine-tune CLKOUT_DISABLE, //clk_output_mode_select *** adi,clk-output-mode-select /* Gain Control */ 2, //gc_rx1_mode *** adi,gc-rx1-mode 2, //gc_rx2_mode *** adi,gc-rx2-mode 58, //gc_adc_large_overload_thresh *** adi,gc-adc-large-overload-thresh 4, //gc_adc_ovr_sample_size *** adi,gc-adc-ovr-sample-size 47, //gc_adc_small_overload_thresh *** adi,gc-adc-small-overload-thresh 8192, //gc_dec_pow_measurement_duration *** adi,gc-dec-pow-measurement-duration 0, //gc_dig_gain_enable *** adi,gc-dig-gain-enable 800, //gc_lmt_overload_high_thresh *** adi,gc-lmt-overload-high-thresh 704, //gc_lmt_overload_low_thresh *** adi,gc-lmt-overload-low-thresh 24, //gc_low_power_thresh *** adi,gc-low-power-thresh 15, //gc_max_dig_gain *** adi,gc-max-dig-gain 0, //gc_use_rx_fir_out_for_dec_pwr_meas_enable *** adi,gc-use-rx-fir-out-for-dec-pwr-meas-enable /* Gain MGC Control */ 2, //mgc_dec_gain_step *** adi,mgc-dec-gain-step 2, //mgc_inc_gain_step *** adi,mgc-inc-gain-step 0, //mgc_rx1_ctrl_inp_enable *** adi,mgc-rx1-ctrl-inp-enable 0, //mgc_rx2_ctrl_inp_enable *** adi,mgc-rx2-ctrl-inp-enable 0, //mgc_split_table_ctrl_inp_gain_mode *** adi,mgc-split-table-ctrl-inp-gain-mode /* Gain AGC Control */ 10, //agc_adc_large_overload_exceed_counter *** adi,agc-adc-large-overload-exceed-counter 2, //agc_adc_large_overload_inc_steps *** adi,agc-adc-large-overload-inc-steps 0, //agc_adc_lmt_small_overload_prevent_gain_inc_enable *** adi,agc-adc-lmt-small-overload-prevent-gain-inc-enable 10, //agc_adc_small_overload_exceed_counter *** adi,agc-adc-small-overload-exceed-counter 4, //agc_dig_gain_step_size *** adi,agc-dig-gain-step-size 3, //agc_dig_saturation_exceed_counter *** adi,agc-dig-saturation-exceed-counter 1000, // agc_gain_update_interval_us *** adi,agc-gain-update-interval-us 0, //agc_immed_gain_change_if_large_adc_overload_enable *** adi,agc-immed-gain-change-if-large-adc-overload-enable 0, //agc_immed_gain_change_if_large_lmt_overload_enable *** adi,agc-immed-gain-change-if-large-lmt-overload-enable 10, //agc_inner_thresh_high *** adi,agc-inner-thresh-high 1, //agc_inner_thresh_high_dec_steps *** adi,agc-inner-thresh-high-dec-steps 12, //agc_inner_thresh_low *** adi,agc-inner-thresh-low 1, //agc_inner_thresh_low_inc_steps *** adi,agc-inner-thresh-low-inc-steps 10, //agc_lmt_overload_large_exceed_counter *** adi,agc-lmt-overload-large-exceed-counter 2, //agc_lmt_overload_large_inc_steps *** adi,agc-lmt-overload-large-inc-steps 10, //agc_lmt_overload_small_exceed_counter *** adi,agc-lmt-overload-small-exceed-counter 5, //agc_outer_thresh_high *** adi,agc-outer-thresh-high 2, //agc_outer_thresh_high_dec_steps *** adi,agc-outer-thresh-high-dec-steps 18, //agc_outer_thresh_low *** adi,agc-outer-thresh-low 2, //agc_outer_thresh_low_inc_steps *** adi,agc-outer-thresh-low-inc-steps 1, //agc_attack_delay_extra_margin_us; *** adi,agc-attack-delay-extra-margin-us 0, //agc_sync_for_gain_counter_enable *** adi,agc-sync-for-gain-counter-enable /* Fast AGC */ 64, //fagc_dec_pow_measuremnt_duration *** adi,fagc-dec-pow-measurement-duration 260, //fagc_state_wait_time_ns *** adi,fagc-state-wait-time-ns /* Fast AGC - Low Power */ 0, //fagc_allow_agc_gain_increase *** adi,fagc-allow-agc-gain-increase-enable 5, //fagc_lp_thresh_increment_time *** adi,fagc-lp-thresh-increment-time 1, //fagc_lp_thresh_increment_steps *** adi,fagc-lp-thresh-increment-steps /* Fast AGC - Lock Level (Lock Level is set via slow AGC inner high threshold) */ 1, //fagc_lock_level_lmt_gain_increase_en *** adi,fagc-lock-level-lmt-gain-increase-enable 5, //fagc_lock_level_gain_increase_upper_limit *** adi,fagc-lock-level-gain-increase-upper-limit /* Fast AGC - Peak Detectors and Final Settling */ 1, //fagc_lpf_final_settling_steps *** adi,fagc-lpf-final-settling-steps 1, //fagc_lmt_final_settling_steps *** adi,fagc-lmt-final-settling-steps 3, //fagc_final_overrange_count *** adi,fagc-final-overrange-count /* Fast AGC - Final Power Test */ 0, //fagc_gain_increase_after_gain_lock_en *** adi,fagc-gain-increase-after-gain-lock-enable /* Fast AGC - Unlocking the Gain */ 0, //fagc_gain_index_type_after_exit_rx_mode *** adi,fagc-gain-index-type-after-exit-rx-mode 1, //fagc_use_last_lock_level_for_set_gain_en *** adi,fagc-use-last-lock-level-for-set-gain-enable 1, //fagc_rst_gla_stronger_sig_thresh_exceeded_en *** adi,fagc-rst-gla-stronger-sig-thresh-exceeded-enable 5, //fagc_optimized_gain_offset *** adi,fagc-optimized-gain-offset 10, //fagc_rst_gla_stronger_sig_thresh_above_ll *** adi,fagc-rst-gla-stronger-sig-thresh-above-ll 1, //fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en *** adi,fagc-rst-gla-engergy-lost-sig-thresh-exceeded-enable 1, //fagc_rst_gla_engergy_lost_goto_optim_gain_en *** adi,fagc-rst-gla-engergy-lost-goto-optim-gain-enable 10, //fagc_rst_gla_engergy_lost_sig_thresh_below_ll *** adi,fagc-rst-gla-engergy-lost-sig-thresh-below-ll 8, //fagc_energy_lost_stronger_sig_gain_lock_exit_cnt *** adi,fagc-energy-lost-stronger-sig-gain-lock-exit-cnt 1, //fagc_rst_gla_large_adc_overload_en *** adi,fagc-rst-gla-large-adc-overload-enable 1, //fagc_rst_gla_large_lmt_overload_en *** adi,fagc-rst-gla-large-lmt-overload-enable 0, //fagc_rst_gla_en_agc_pulled_high_en *** adi,fagc-rst-gla-en-agc-pulled-high-enable 0, //fagc_rst_gla_if_en_agc_pulled_high_mode *** adi,fagc-rst-gla-if-en-agc-pulled-high-mode 64, //fagc_power_measurement_duration_in_state5 *** adi,fagc-power-measurement-duration-in-state5 2, //fagc_large_overload_inc_steps *** adi,fagc-adc-large-overload-inc-steps /* RSSI Control */ 1, //rssi_delay *** adi,rssi-delay 1000, //rssi_duration *** adi,rssi-duration 3, //rssi_restart_mode *** adi,rssi-restart-mode 0, //rssi_unit_is_rx_samples_enable *** adi,rssi-unit-is-rx-samples-enable 1, //rssi_wait *** adi,rssi-wait /* Aux ADC Control */ 256, //aux_adc_decimation *** adi,aux-adc-decimation 40000000UL, //aux_adc_rate *** adi,aux-adc-rate /* AuxDAC Control */ 1, //aux_dac_manual_mode_enable *** adi,aux-dac-manual-mode-enable 0, //aux_dac1_default_value_mV *** adi,aux-dac1-default-value-mV 0, //aux_dac1_active_in_rx_enable *** adi,aux-dac1-active-in-rx-enable 0, //aux_dac1_active_in_tx_enable *** adi,aux-dac1-active-in-tx-enable 0, //aux_dac1_active_in_alert_enable *** adi,aux-dac1-active-in-alert-enable 0, //aux_dac1_rx_delay_us *** adi,aux-dac1-rx-delay-us 0, //aux_dac1_tx_delay_us *** adi,aux-dac1-tx-delay-us 0, //aux_dac2_default_value_mV *** adi,aux-dac2-default-value-mV 0, //aux_dac2_active_in_rx_enable *** adi,aux-dac2-active-in-rx-enable 0, //aux_dac2_active_in_tx_enable *** adi,aux-dac2-active-in-tx-enable 0, //aux_dac2_active_in_alert_enable *** adi,aux-dac2-active-in-alert-enable 0, //aux_dac2_rx_delay_us *** adi,aux-dac2-rx-delay-us 0, //aux_dac2_tx_delay_us *** adi,aux-dac2-tx-delay-us /* Temperature Sensor Control */ 256, //temp_sense_decimation *** adi,temp-sense-decimation 1000, //temp_sense_measurement_interval_ms *** adi,temp-sense-measurement-interval-ms 0xCE, //temp_sense_offset_signed *** adi,temp-sense-offset-signed 1, //temp_sense_periodic_measurement_enable *** adi,temp-sense-periodic-measurement-enable /* Control Out Setup */ 0xFF, //ctrl_outs_enable_mask *** adi,ctrl-outs-enable-mask 0, //ctrl_outs_index *** adi,ctrl-outs-index /* External LNA Control */ 0, //elna_settling_delay_ns *** adi,elna-settling-delay-ns 0, //elna_gain_mdB *** adi,elna-gain-mdB 0, //elna_bypass_loss_mdB *** adi,elna-bypass-loss-mdB 0, //elna_rx1_gpo0_control_enable *** adi,elna-rx1-gpo0-control-enable 0, //elna_rx2_gpo1_control_enable *** adi,elna-rx2-gpo1-control-enable 0, //elna_gaintable_all_index_enable *** adi,elna-gaintable-all-index-enable /* Digital Interface Control */ 0, //digital_interface_tune_skip_mode *** adi,digital-interface-tune-skip-mode 0, //digital_interface_tune_fir_disable *** adi,digital-interface-tune-fir-disable 1, //pp_tx_swap_enable *** adi,pp-tx-swap-enable 1, //pp_rx_swap_enable *** adi,pp-rx-swap-enable 0, //tx_channel_swap_enable *** adi,tx-channel-swap-enable 0, //rx_channel_swap_enable *** adi,rx-channel-swap-enable 1, //rx_frame_pulse_mode_enable *** adi,rx-frame-pulse-mode-enable 0, //two_t_two_r_timing_enable *** adi,2t2r-timing-enable 0, //invert_data_bus_enable *** adi,invert-data-bus-enable 0, //invert_data_clk_enable *** adi,invert-data-clk-enable 0, //fdd_alt_word_order_enable *** adi,fdd-alt-word-order-enable 0, //invert_rx_frame_enable *** adi,invert-rx-frame-enable 0, //fdd_rx_rate_2tx_enable *** adi,fdd-rx-rate-2tx-enable 1, //swap_ports_enable *** adi,swap-ports-enable 0, //single_data_rate_enable *** adi,single-data-rate-enable 0, //lvds_mode_enable *** adi,lvds-mode-enable 0, //half_duplex_mode_enable *** adi,half-duplex-mode-enable 0, //single_port_mode_enable *** adi,single-port-mode-enable 1, //full_port_enable *** adi,full-port-enable 0, //full_duplex_swap_bits_enable *** adi,full-duplex-swap-bits-enable 0, //delay_rx_data *** adi,delay-rx-data 0, //rx_data_clock_delay *** adi,rx-data-clock-delay 4, //rx_data_delay *** adi,rx-data-delay 7, //tx_fb_clock_delay *** adi,tx-fb-clock-delay 0, //tx_data_delay *** adi,tx-data-delay #ifdef ALTERA_PLATFORM 300, //lvds_bias_mV *** adi,lvds-bias-mV #else 150, //lvds_bias_mV *** adi,lvds-bias-mV #endif 1, //lvds_rx_onchip_termination_enable *** adi,lvds-rx-onchip-termination-enable 0, //rx1rx2_phase_inversion_en *** adi,rx1-rx2-phase-inversion-enable 0xFF, //lvds_invert1_control *** adi,lvds-invert1-control 0x0F, //lvds_invert2_control *** adi,lvds-invert2-control /* GPO Control */ 0, //gpo_manual_mode_enable *** adi,gpo-manual-mode-enable 0, //gpo_manual_mode_enable_mask *** adi,gpo-manual-mode-enable-mask 0, //gpo0_inactive_state_high_enable *** adi,gpo0-inactive-state-high-enable 0, //gpo1_inactive_state_high_enable *** adi,gpo1-inactive-state-high-enable 0, //gpo2_inactive_state_high_enable *** adi,gpo2-inactive-state-high-enable 0, //gpo3_inactive_state_high_enable *** adi,gpo3-inactive-state-high-enable 0, //gpo0_slave_rx_enable *** adi,gpo0-slave-rx-enable 0, //gpo0_slave_tx_enable *** adi,gpo0-slave-tx-enable 0, //gpo1_slave_rx_enable *** adi,gpo1-slave-rx-enable 0, //gpo1_slave_tx_enable *** adi,gpo1-slave-tx-enable 0, //gpo2_slave_rx_enable *** adi,gpo2-slave-rx-enable 0, //gpo2_slave_tx_enable *** adi,gpo2-slave-tx-enable 0, //gpo3_slave_rx_enable *** adi,gpo3-slave-rx-enable 0, //gpo3_slave_tx_enable *** adi,gpo3-slave-tx-enable 0, //gpo0_rx_delay_us *** adi,gpo0-rx-delay-us 0, //gpo0_tx_delay_us *** adi,gpo0-tx-delay-us 0, //gpo1_rx_delay_us *** adi,gpo1-rx-delay-us 0, //gpo1_tx_delay_us *** adi,gpo1-tx-delay-us 0, //gpo2_rx_delay_us *** adi,gpo2-rx-delay-us 0, //gpo2_tx_delay_us *** adi,gpo2-tx-delay-us 0, //gpo3_rx_delay_us *** adi,gpo3-rx-delay-us 0, //gpo3_tx_delay_us *** adi,gpo3-tx-delay-us /* Tx Monitor Control */ 37000, //low_high_gain_threshold_mdB *** adi,txmon-low-high-thresh 0, //low_gain_dB *** adi,txmon-low-gain 24, //high_gain_dB *** adi,txmon-high-gain 0, //tx_mon_track_en *** adi,txmon-dc-tracking-enable 0, //one_shot_mode_en *** adi,txmon-one-shot-mode-enable 511, //tx_mon_delay *** adi,txmon-delay 8192, //tx_mon_duration *** adi,txmon-duration 2, //tx1_mon_front_end_gain *** adi,txmon-1-front-end-gain 2, //tx2_mon_front_end_gain *** adi,txmon-2-front-end-gain 48, //tx1_mon_lo_cm *** adi,txmon-1-lo-cm 48, //tx2_mon_lo_cm *** adi,txmon-2-lo-cm /* GPIO definitions */ { .number = -1, .platform_ops = GPIO_OPS, .extra = GPIO_PARAM }, //gpio_resetb *** reset-gpios /* MCS Sync */ { .number = -1, .platform_ops = GPIO_OPS, .extra = GPIO_PARAM }, //gpio_sync *** sync-gpios { .number = -1, .platform_ops = GPIO_OPS, .extra = GPIO_PARAM }, //gpio_cal_sw1 *** cal-sw1-gpios { .number = -1, .platform_ops = GPIO_OPS, .extra = GPIO_PARAM }, //gpio_cal_sw2 *** cal-sw2-gpios { .device_id = SPI_DEVICE_ID, .mode = NO_OS_SPI_MODE_1, .chip_select = SPI_CS, .platform_ops = SPI_OPS, .extra = SPI_PARAM }, /* External LO clocks */ NULL, //(*ad9361_rfpll_ext_recalc_rate)() NULL, //(*ad9361_rfpll_ext_round_rate)() NULL, //(*ad9361_rfpll_ext_set_rate)() #ifndef AXI_ADC_NOT_PRESENT &rx_adc_init, // *rx_adc_init &tx_dac_init, // *tx_dac_init #endif };
При этом чтобы вывести отсчёты в терминал, необходимо дописать в последний блок кода примера TDD_SWITCH_STATE_EXAMPLE строки, которые для удобства выведут в терминал уровень принимаемого сигнала, затем прочитают данные из DMA, данные проверятся и в цикле выведется 1024 отсчёта из необходимого нам канала. Это происходит в строке 21 в коде ниже. То есть всего 4 канала, i0, q0, i1, q1. А по этому условию будут напечатаны отсчёты из одного канала.
struct rf_rssi ch0; struct rf_rssi ch1; ad9361_get_rx_rssi(ad9361_phy, 0, &ch0); ad9361_get_rx_rssi(ad9361_phy, 1, &ch1); printf("ch0: %lu %lu %lu %ld %d\n", ch0.ant, ch0.symbol, ch0.preamble, ch0.multiplier, ch0.duration); printf("ch1: %lu %lu %lu %ld %d\n", ch1.ant, ch1.symbol, ch1.preamble, ch1.multiplier, ch1.duration); /* Read the data from the ADC DMA. */ axi_dmac_transfer_start(rx_dmac, &read_transfer); /* Wait until transfer finishes */ status = axi_dmac_transfer_wait_completion(rx_dmac, 500); if(status < 0) return status; Xil_DCacheInvalidateRange((uintptr_t)adc_buffer, sizeof(adc_buffer)); //---------------------------------------------------------------------------------------------------- for(int i = 0; i < 4096; i++){ if(i%4==0){ int16_t sample = (int16_t)adc_buffer[i]; printf("%d\n", sample); } } //----------------------------------------------------------------------------------------------------
Весь код для TDD_SWITCH_STATE_EXAMPLE
#ifdef TDD_SWITCH_STATE_EXAMPLE // uint32_t ensm_mode; struct no_os_gpio_init_param gpio_init = { .platform_ops = GPIO_OPS, .extra = GPIO_PARAM }; struct no_os_gpio_desc *gpio_enable_pin; struct no_os_gpio_desc *gpio_txnrx_pin; if (!ad9361_phy->pdata->fdd) { if (ad9361_phy->pdata->ensm_pin_ctrl) { gpio_init.number = GPIO_ENABLE_PIN; status = no_os_gpio_get(&gpio_enable_pin, &gpio_init); if (status != 0) { printf("no_os_gpio_get() error: %"PRIi32"\n", status); return status; } no_os_gpio_direction_output(gpio_enable_pin, 1); gpio_init.number = GPIO_TXNRX_PIN; status = no_os_gpio_get(&gpio_txnrx_pin, &gpio_init); if (status != 0) { printf("no_os_gpio_get() error: %"PRIi32"\n", status); return status; } no_os_gpio_direction_output(gpio_txnrx_pin, 0); no_os_udelay(10); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); if (ad9361_phy->pdata->ensm_pin_pulse_mode) { while(1) { no_os_gpio_set_value(gpio_txnrx_pin, 0); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 0); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX Pulse control - RX: %s\n", ensm_mode == ENSM_MODE_RX ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 0); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX Pulse control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_txnrx_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 0); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX Pulse control - TX: %s\n", ensm_mode == ENSM_MODE_TX ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 0); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX Pulse control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); } } else { while(1) { no_os_gpio_set_value(gpio_txnrx_pin, 0); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX control - RX: %s\n", ensm_mode == ENSM_MODE_RX ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_enable_pin, 0); no_os_udelay(10); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_txnrx_pin, 1); no_os_udelay(10); no_os_gpio_set_value(gpio_enable_pin, 1); no_os_udelay(10); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX control - TX: %s\n", ensm_mode == ENSM_MODE_TX ? "OK" : "Error"); no_os_mdelay(1000); no_os_gpio_set_value(gpio_enable_pin, 0); no_os_udelay(10); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("TXNRX control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); } } } else { // int i = 1; while(1) { ad9361_set_en_state_machine_mode(ad9361_phy, ENSM_MODE_RX); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("SPI control - RX: %s\n", ensm_mode == ENSM_MODE_RX ? "OK" : "Error"); no_os_mdelay(1000); struct rf_rssi ch0; struct rf_rssi ch1; ad9361_get_rx_rssi(ad9361_phy, 0, &ch0); ad9361_get_rx_rssi(ad9361_phy, 1, &ch1); printf("ch0: %lu %lu %lu %ld %d\n", ch0.ant, ch0.symbol, ch0.preamble, ch0.multiplier, ch0.duration); printf("ch1: %lu %lu %lu %ld %d\n", ch1.ant, ch1.symbol, ch1.preamble, ch1.multiplier, ch1.duration); /* Read the data from the ADC DMA. */ axi_dmac_transfer_start(rx_dmac, &read_transfer); /* Wait until transfer finishes */ status = axi_dmac_transfer_wait_completion(rx_dmac, 500); if(status < 0) return status; Xil_DCacheInvalidateRange((uintptr_t)adc_buffer, sizeof(adc_buffer)); //---------------------------------------------------------------------------------------------------- for(int i = 0; i < 4096; i++){ if(i%4==2){ int16_t sample = (int16_t)adc_buffer[i]; printf("%d\n", sample); } } //---------------------------------------------------------------------------------------------------- ad9361_set_en_state_machine_mode(ad9361_phy, ENSM_MODE_ALERT); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("SPI control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); ad9361_set_en_state_machine_mode(ad9361_phy, ENSM_MODE_TX); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("SPI control - TX: %s\n", ensm_mode == ENSM_MODE_TX ? "OK" : "Error"); no_os_mdelay(1000); ad9361_set_en_state_machine_mode(ad9361_phy, ENSM_MODE_ALERT); ad9361_get_en_state_machine_mode(ad9361_phy, &ensm_mode); printf("SPI control - Alert: %s\n", ensm_mode == ENSM_MODE_ALERT ? "OK" : "Error"); no_os_mdelay(1000); } } } #endif
В Git Bash выполним команду мake в каталоге проекта ad9361.

В терминал будет выведена кое-какая отладочная информация о процессах сборки, линковки и компиляции проекта, и некоторые предупреждения, которые пока что можно проигнорировать. А в конце будет информация об успешном создании бинарного файла для нашего микропроцессора. Давайте посмотрим, что будет, если загрузить программу в ZYNQ. Для этого в терминал надо дописать команды:
export XSCT_REMOTE_HOST=127.0.0.1 export XSCT_REMOTE_PORT=3121
Об этом тоже написано на https://wiki.analog.com/resources/no-os/build в разделе Running/Debugging. Подключаем программатор, если уже успели его отключить, и питание отладочной платы. Проверяем COM-порт, на котором сидит клон PlutoSDR и подключаемся к com-порту любимым терминалом на скорости 115200. В Git Bash пишем команду make run.
При первой прошивке после подключения программатора он пару раз сам отключается и подключается к системе. А в Git Bash появляется информация о якобы успешной загрузке, хотя это не так. Я просто это игнорирую и запускаю команду ещё раз.
Кстати, если при экспорте из Vivado .xsa файла Вы назвали не system_top.xsa, то можете получить couldn't open <путь_до_Вашего_bit>: no such file or directory

Это не страшно, просто надо переименовать Ваш <название_Вашего>.bit в system_top.bit и запустить make run ещё раз. И на этот раз синий светодиод на отладочной плате вспыхнет ярким синим светом, а в COM-порт будет выведено заветное ad9361_init : AD936x Rev 2 successfully initialized. Но информация в терминале не вся.

Не вся, потому что при запуске одного только примера DMA_EXAMPLE в терминал должно быть напечатано DAC_DMA_EXAMPLE: address=0x161030 samples=65536 channels=4 bits=16. Это видно в исходном коде на строке 812
printf("DAC_DMA_EXAMPLE: address=%#lx samples=%lu channels=%u bits=%lu\n", (uintptr_t)adc_buffer, NO_OS_ARRAY_SIZE(adc_buffer), rx_adc_init.num_channels, 8 * sizeof(adc_buffer[0]));

Я это заметил ещё когда только начинал ��аниматься программированием этих микросхем несколько лет назад. Отлаживаться без IDE мне показалось не очень удобно, и я перешёл в Vitis. С тех пор я так и не запускал сборку приложений в Git Bash терминале. И этот баг не искал, скорее всего, надо ковырять makefile. Может быть, кто-нибудь это сделает и поделится в комментариях.
Давайте соберём это приложение в Vitis. Начинается всё точно так же, как и Hello World, но создать надо empty приложение.

А дальше необходимо добавить все необходимые исходные файлы. Они доступны в Github https://github.com/NSV47/for_article_11_on_Habr.git
Я добавил исходные файлы, которыми сам много пользовался в Github. Но их лучше перепроверить. Потому что где-то выводится лишняя отладочная информация.




После добавления необходимо выполнить Build, но IDE не очень нравятся пути, поэтому их необходимо переписывать.


Когда все пути исправлены, всё завершается без ошибок.

Вот теперь, если включить все необходимые примеры, о которых мы говорили раньше, в терминал будут печататься значения отсчётов по необходимому каналу.


Чтобы построить график из отсчётов можно их скопировать в файл и воспользоваться очень простым скриптом на Python.
import numpy as np from matplotlib import pyplot as plt # Читаем данные из файла with open('data2.txt', 'r') as f: data = [int(line.strip()) for line in f.readlines()] # Рисуем график plt.figure(figsize=(12, 6)) plt.plot(data, 'b-', linewidth=0.5) plt.xlabel('Отсчеты') plt.ylabel('Значение') plt.title('Данные из data1.txt') plt.grid(True, alpha=0.3) plt.axhline(y=0, color='r', linestyle='--', alpha=0.5) plt.show()



Подведём итоги. Что же это всё значит? А это значит, что микросхема получила по SPI от ARM-ядра, которое мы с Вами успешно запрограммировали в свои регистры необходимые данные для инициализации и работы в режимах приёма и передачи. Встроенный конечный автомат управляет микросхемой по SPI и переводит её поочерёдно в режимы приёма и передачи. Ядро axi_ad9361 инициализировано и отдаёт данные, которые мы с Вами при данной реализации можем считать через DMA и напечатать. Про ad9361 спето очень много хвалебных песен, это микросхема заслужившая уважение, но порог вхождения без изучения примеров остаётся сравнительно высоким.
Меня на протяжении длительного времени преследовали ошибки инициализации ядра axi_ad9361. В структуре
AD9361_InitParamесть параметрdigital_interface_tune_skip_mode, который при определённом HDL дизайне необходимо активировать. Причём даже при эталонном дизайне можно наткнуться на неожиданные ошибки, например, при неверной настройке Digital Interface Control, в терминал напечатается сообщение под спойлером ниже.
Возможная ошибка
cf-ad9361-lpc: Successfully initialized (61445617 Hz) SAMPL CLK: 30720000 tuning: RX 0:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f: 0:# # # # # # # # # # # # # # # # 1:# # # # # # # # # # # # # # # # ad9361_dig_tune_delay: Tuning RX FAILED! ad9361_init : AD936x initialization error
И первое, что захочется сделать — это пропустить настройку цифрового интерфейса. Тогда даже пройдёт сообщение об инициализации микросхемы и HDL ядер, но это прямая дорога в тупик при разработке под ad9361. Ошибки при настройке цифрового интерфейса прямо указывают на проблемы передачи информации, и игнорировать их нельзя!
Эта статья — капля в океане регистров и моря возможностей ad9361. И если Вам будет интересно, мы могли бы поговорить в следующих статьях об использовании этой микросхемы для передачи различных данных, будь то символы для передачи сообщений или даже простая передача видеосигнала, используя bare-metal приложение.
Поэтому, пожалуйста, не стесняйтесь задавать свои вопросы в комментариях. Даже если я не смогу на них ответить, может быть, это увидят люди, которые более глубоко погрузились в использование этой микросхемы.
Спасибо.
С. Н.
