При помощи какого параметра можно проверить корректность установки счетчика через отладочное сообщение в консоли?
Оказываю помощь в получении сертификатов Яндекса, Google и YouTube.
Занимаюсь SEO-продвижением сайтов с 2013 года.
Всегда рад обратной связи!
Помогаю проходить тесты по сертификациям Яндекса, Гугла и Ютуба. Собираю актуальные вопросы и ответы на тесты по Директу, Метрике, Аналитикс и другим экзаменам.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
База вопросов (ноябрь 2023)
- Вопросы по Яндекс.Директу (528)
- Вопросы по Яндекс.Метрике (256)
- Вопросы по Яндекс.Дзену (85)
- Вопросы по медийной рекламе Яндекса (101)
- Вопросы по Google Analytics IQ (174)
- Вопросы по Google Поисковой рекламе (152)
- Вопросы по Google Видеорекламе (134)
- Вопросы по рекламе в КМС (132)
- Вопросы по оценке эффективности объявлений в Google Рекламе (104)
- Вопросы по Youtube Как управлять контентом (94)
- Answers to Google Analytics IQ (99)
- Ответы на сертификацию Яндекс Директ – ноябрь 2023
- Ответы на сертификацию Яндекс Метрика – ноябрь 2023
- Ответы на сертификацию по рекламе мобильных приложений в Яндексе – ноябрь 2023
- Ответы на сертификацию Google Analytics IQ – ноябрь 2023
- Ответы на сертификацию по медийной рекламе Яндекса – ноябрь 2023
При помощи какого параметра можно проверить корректность установки счетчика через отладочное сообщение в консоли?
При помощи какого параметра можно проверить корректность установки счетчика через отладочное сообщение в консоли?
Понравилась статья? Поделиться с друзьями:
Вам также может быть интересно
Ссылка на отчёт. Даты: апрель 2018 года, атрибуция — последний переход. Сколько визитов останется
На сайте в качестве цели задано посещение страницы «Контакты». В 11:00 посетитель перешел на
За день на сайте по цели было зафиксировано 15 визитов, 10 посетителей, 5 целевых
В коде счетчика указана следующая информация: ym(XXXXXX, ‘init’,
Магазин «Уютно и тепло» торгует товарами для дома и дачи. На сайте для каждого
В каком стандартном отчете можно увидеть визиты только по тем кликам, которые не были
Zynq 7000. Обмен информацией между PS и PL
Продолжаю повествование о том, как проходит мое изучение возможностей отладочной платы с SoC Zynq 7000 на базе отладочной платы QMTech. В этой статье я опишу то, как я решал задачу примитивного обмена данными между PS и PL с использованием baremetal application и при использовании Linux. Всем интересующимся добро пожаловать под кат!

Дисклеймер
Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте, с чего можно начать, при изучении отладочных плат на базе Zynq. Я не являюсь профессиональным разработчиком под ПЛИС и SoC Zynq, не являюсь системным программистом под Linux и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется.
Перед тем как приступить к выполнению каких-либо действий из этого урока — настоятельно рекомендую прочитать предыдущие уроки из серии уроков по Zynq. Инженер, который хочет повторить изложенное в этом уроке, уже должен уметь или хотя бы иметь представление о том, как:
- Работать с Xilinx Vivado и иметь понимание того, как и в какой последовательность организуется разработка с использованием данной среды;
- Что такое Xilinx SDK и как она взаимосвязана с Xilinx Vivado;
- Собрать baremetal-приложение для Zynq, как его откомпилировать и запустить на отладке;
- Собрать загрузочный образ Linux с FSBL, bitstream-файлом, U-Boot, Device Tree, Root FS и ядром;
Постановка задачи
Чтобы развивать дальнейшее повествование и изучать что-то новое нужна была реальная интересная задача. И я ее придумал! Ребята из моей команды рассказывали в этой статье историю того, как шла разработка Яндекс.Станции-Max и упомянули устройства для организации тестовых испытаний плат, так называемые “джиги” (англ. jigs).
Это устройство организовано таким образом, что есть специальная платформа, которая встаёт иглами на тестовые точки и делает через иглы определенные манипуляции для проведения тестирования. Не вдаваясь в подробности, в конечном итоге данные измерений преобразуются в специально сформированный набор импульсов и отправляются в процессор на обработку. Основная задача промежуточных вычислений до наступления момента интерпретации результатов тестирования — сосчитать количество импульсов с того или иного канала. Но с наращиванием количества каналов счёта таких импульсов есть ограничение в возможностях расширения. Плюсом к этому обработка импульсов идет через процессор с ядром Cortex-M4 и ограниченным набором по периферии, т.е. о реальной параллельности счёта большого числа каналов не может идти и речи. И тут я подумал о том, что можно же считать параллельно при помощи ПЛИС и забирать данные в Linux и отсылать уже данные по сети на сервер при помощи Zynq!
И тут в голове оформилась интересная задача:
- Нужно организовать внутри ПЛИС набор счетчиков, которые будут считать импульсы с частотой до 2МГц;
- Счетчики должны быть включаемыми\выключаемыми;
- Счетчики должны быть сбрасываемыми;
- Для включения, выключения и сброса счетчиков должны быть отдельные регистры управления;
- Счётчики должны работать независимо друг от друга и синхронно;
- Управление и считывание данных из счётчиков должно быть доступно из PS или Linux;
- Счётчики должны считать точное количество импульсов (допустимое отклонение 1-2 импульса).
Исходя из постановки задачи был сформирован четкий план действий:
- Создаем проект, настраиваем Processing System и Processor System Reset.
- Настраиваем соответствующие пины ПЛИС, которые будут входами для импульсов;
- В ПЛИС делаем блок ограничения импульсов частотой более 2 МГц и проверяем генератором сигналов или другой ПЛИС;
- Добавляем счетчик импульсов в ПЛИС и проверяем корректность счёта сгенерировав конкретное количество импульсов другой ПЛИС;
- Организуем некую память в которую можно записывать и из которой можно читать данные как из ПЛИС, так и из PS-части. В этой же памяти внутри ПЛИС организуем две ячейки памяти которые будут выступать в качестве двух регистров управления (сброс, включение и выключение) и одну в качестве регистра хранения команд;
- Организуем блок управления счетчиками внутри ПЛИС, который сможет опрашивать память на предмет наличия команды из PS, и по команде “забирать” данные из счётчиков и сохранять их в память, сбрасывать значение и включаться\выключаться.
- Протестировать полученный результат с использованием baremetal-приложения и из Linux.
Что ж, общее понимание, того что нужно сделать есть, можно приступить к реализации намеченного.
Подготовительный этап
Создаем новый проект к нашей плате по шаблону. Как это сделать — описано в предыдущих статьях. Подробно описывать этот шаг я не буду и сразу перейдем к формированию Block Design.
Нажимаем кнопку Create Block Design и в первую очередь добавляем IP-ядро с именем Zynq Processing System. После этого его можно быстро сконфигурировать с помощью tcl-скрипта. Я заготовил Preset для быстрой настройки для платы QMTech и поэтому в меню нажимаем Presets — Apply Configuration и выбираем файл который вы можете взять отсюда.

После этого нужно перейти в меню Clock Configuration — PL Fabric Clocks и включаем FCLK_CLK1 на той же частоте что и FCLK_CLK0. Этот тактовый сигнал нам понадобится для подключения логического анализатора (ILA — Integrated Logic Analyzer) и отладки полученного автомата.

После этого добавим в наш дизайн блок Processor System Reset. Выполняем предложенные автоматизации и должно получиться следующее:

Далее можно переходить к настройкам пинов через добавление Physical Constraints. Добавляем файл physical_constr и переходим в него из древа проектов для редактирования.

В файл physical_constr записываем следующее:
set_property -dict < PACKAGE_PIN P20 IOSTANDARD LVCMOS33 >[get_ports < pulse_1 >]; set_property -dict < PACKAGE_PIN N20 IOSTANDARD LVCMOS33 >[get_ports < pulse_2 >]; set_property -dict < PACKAGE_PIN T19 IOSTANDARD LVCMOS33 >[get_ports < pulse_3 >];
Поясню немного по содержанию. В этом файле мы указываем имена портов которые будут использованы в дизайне и сопоставляем их реальным физическим выводам Zynq. В данном случае мы добавляем три порта для счётчиков.
Обработаем входные сигналы и сосчитаем их
Теперь можно начать с обработки входных импульсов. В первую очередь, нужно сделать модуль антизвона и ограничить частоты входных сигналов. Это сделать довольно легко, необходимо лишь перенести в проект модуль debouncer, который фигурировал в предыдущих статьях. Создаем новый source-файл, именуем его как debouncer и открываем на редактирование. Подробное объяснение по работе данного автомата я делал в этой статье и код обильно снабжен поясняющими комментариями. На разборе этого кода я останавливаться не буду.

Исходный код модуля debouncer. Ссылка.
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: - // Engineer: megaloid // // Create Date: 08/14/2021 02:39:31 PM // Design Name: Zynq Multichannel Counter // Module Name: debouncer // Project Name: // Target Devices: // Tool Versions: // Description: Debouncer for input signals from pins // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module debouncer // Параметры #( parameter CNT_WIDTH = 4 // Разрядность счётчика, выбрана подходящая для того, // чтобы можно было пропустить высокую частоту счетчика но не больше 2МГц ) // Порты ( input clk_i, // Clock input input rst_i, // Reset input input sw_i, // Switch input output reg sw_state_o, // Состояние нажатия клавиши output reg sw_down_o, // Импульс "кнопка нажата" output reg sw_up_o // Импульс "кнопка отпущена" ); reg [1:0] sw_r; // Триггер для исключения метастабильных состояний always @ (negedge rst_i or posedge clk_i) if (~rst_i) sw_r ; reg [CNT_WIDTH-1:0] sw_count; // Счетчик для фиксации состояния wire sw_change_f = (sw_state_o != sw_r[1]); wire sw_cnt_max = &sw_count; always @(negedge rst_i or posedge clk_i) // Каждый положительный фронт сигнала clk_i проверяем, состояние на входе sw_i if (~rst_i) begin sw_count
Следующим шагом нам необходимо сосчитать все импульсы которые прилетают из debouncer-модуля. Создаем еще один файл с исходным кодом и называем его counter. Записываем в него код достаточно простого автомата. Ссылка.
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: - // Engineer: megalloid // // Create Date: 08/14/2021 02:51:56 PM // Design Name: Zynq Multichannel Counter // Module Name: counter // Project Name: - // Target Devices: - // Tool Versions: - // Description: Pulses counter // // Dependencies: - // // Revision: - // Revision 0.01 - File Created // Additional Comments: - // ////////////////////////////////////////////////////////////////////////////////// module counter( input pulse_i, // Входной порт для сгенерированных импульсов input rst_i, // Вход для сигнала сброса значения счетчика input ena_i, // Вход для сигнала на разрешение считать импульсы output [31:0] cnt_o // Выходной сигнал значения счётчика ); reg[31:0] cnt_r = 0; // Регистр для хранения значения счётчика assign cnt_o = cnt_r; // Присваиваем регистр к выходному порту always @ (posedge pulse_i or negedge rst_i) begin // Каждый раз когда будет получен сигнал сброса или импульса if (~rst_i) begin cnt_r
После этого мы можем добавить в дизайн интересующее нас количество debouncer-ов и счётчиков. Я остановился на трёх каналах. Добавим эти блоки в наш дизайн, соединим порты проводниками, сделаем входные порты для debouncer-ов, переименуем их и получим следующее:

Теперь переходим к организации памяти для хранения данных счетчиков.
AXI-интерфейс, BRAM и вот это всё
Для того, чтобы осуществить обмен информации между PS и PL используется универсальный интерфейс AXI. Данный интерфейс предлагает широкий спектр функций, для организации информационного обмена внутри Zynq и по сути является универсальной шиной для самых разнообразных применений, с высокой пропускной способностью и минимальными задержками.
Глубокое рассмотрение принципов работы интерфейса AXI не планировалось в этой статье, и в Интернете очень много информации на этот счёт, и я решил не углубляться в описание возможностей AXI. Но за то расскажу о том, как применение этого интерфейса помогло мне решить поставленную задачу.
Для организации нужного нам обмена мы задействуем уже готовый, предложенный компанией Xilinx, блок AXI SmartConnect, который является преемником AXI Interconnect. В нашем случае будет достаточно использовать самый простой вариант - AXI4-Lite, который достаточно прост, и подходит для передачи небольших порций информации и позволяет только читать и записывать 32-битные слова за раз. Используется обычно для доступа к низкоскоростным периферийным устройствам. Самое то для получения и отправки команд счетчиками и выгрузки значений трёх счётчиков.
Не будем останавливаться на теории и перейдем к выполнению задачи. Добавим в наш дизайн IP-блок AXI SmartConnect и сразу зайдем в его настройки и выберем количество Slave-интерфейсов равным 1. Подключаем сигнал сброса и тактирования следующим образом:

Для хранения информации внутри ПЛИС мы можем задействовать AXI BRAM Controller и Block Memory Generator. Добавим их в дизайн и перейдем к первоначальной настройке.
Двойным кликом заходим в настройки AXI BRAM Controller-а и настраиваем настройки следующим образом:

Выбираем AXI Protocol AXI4LITE. И делаем один BRAM Interface. Остальные настройки оставляем по умолчанию. Нажимаем ОК.
Соединяем интерфейсы Master AXI с Slave AXI и подключаем тактовый сигнал и сигнал сброса к AXI BRAM Controller:

Переходим к настройке Block Memory Generator. Выбираем Memory Type как True Dual Port RAM и остальные настройки оставляем по умолчанию.

Подключаем соединения и получаем следующий вид:

Следующим шагом нужно определить какое количество памяти будет задействовано для хранения данных в BRAM. Для этого нужно зайти в верхнее меню Window - Address Editor и автоматически назначить адреса:

После этого произойдет автоматическая разметка по адресу смещения 0x4000_0000 в размере 8К. Нам этого будет достаточно для записи. Оставим значение по умолчанию.
Блок управления счетчиками
Для того, чтобы осуществить доступ к BRAM из нашей кастомной RTL-ки необходимо определить какие сигналы должны быть подключены. Итак, нам нужен BRAM_PORTB и именно по этому порту мы будем взаимодействовать с памятью. Развернем его и после этого можно увидеть набор сигналов:

Собственно все эти сигналы мы должны сформировать для того, чтобы работать с памятью. В первую очередь необходимо добавить два сигнала для Enable B Port (enb) и Reset B Port (rstb).
Для enb сделаем константу как сигнал разрешающий постоянную работу. Добавим IP-блок Contsant и откроем настройки. Выставляем ширину шины в 1 и сигнал по умолчанию - 1.

Для сигнала rstb - делаем логический элемент NOT через IP-блок Utility Vector Logic. Заходим в настройки выставляем тип NOT и ширину так же в 1.

Подключаем следующим образом:

Теперь можно перейти к созданию RTL-ки которая будет являться ключевым элементом всего проекта. Назовём его counter_mgmt. Итого нам нужно сделать целый список портов:
- Вход для сигнала тактирования;
- Вход для сброса;
- Входные 32-битные шины с текущим значением счётчиков для последующей записи в память;
- Выходные сигналы включения\выключения счётчиков;
- Выходные сигналы для сброса значения счётчиков;
- Выходной сигнал Write Enable для Block Memory Generator, сигнализирующий ему о том, что будет производиться запись в память;
- Выходная 32-битная шина Address будет сообщать в какую ячейку памяти нужно будет проводить запись или из которой нужно производить чтение;
- 32-битная шина входных и выходных данных, которые будут использоваться при записи и чтении информации из BRAM;
Оформляем всё это и получается следующий набор входных портов:
////////////////////////////////////////////////////////////////////////////////// // Company: - // Engineer: Andrey Zaostrovnykh // // Create Date: 08/14/2021 02:39:31 PM // Design Name: Zynq Multichannel Counter // Module Name: counter_mgmt // Project Name: // Target Devices: // Tool Versions: // Description: Management module for counters // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module counter_mgmt( input clk_i, // Вход для сигнала тактирования input rst_i, // Вход для сброса автомата input [31:0] cnt_0_data, // Значение 1-го счетчика input [31:0] cnt_1_data, // Значение 2-го счетчика input [31:0] cnt_2_data, // Значение 3-го счетчика output cnt_0_en, // Включение 1-го счетчика output cnt_0_rst, // Сброс 1-го счетчика output cnt_1_en, // Включение 1-го счетчика output cnt_1_rst, // Сброс 1-го счетчика output cnt_2_en, // Включение 1-го счетчика output cnt_2_rst, // Сброс 1-го счетчика output we, // Сигнал Write Enable output [31:0] addr, // Адрес чтения-записи output [31:0] dout, // Выходные данные для записи в память input [31:0] din // Входные данные при чтении из памяти ); endmodule
Сохраняем код и добавляем его на общую диаграмму дизайна. Соединяем его с Block Memory Generator следующим образом:

После определения набора входных\выходных сигналов можно подумать об общей логике работы все автомата в целом.
Задумка получается следующая. Работа всего автомата в целом будет управляться через регистр, который будет являться ячейкой памяти в BRAM. То есть, наш автомат будет в постоянном режиме опрашивать эту ячейку, и если будут обнаружены какие-то команды, которые запишет PS в эту ячейку - мы будем выполнять ту или иную команду. Вторая и третья ячейка памяти будет отвечать за включение\выключение счетчиков, и сброс значения счетчика соответственно. Четвертая, пятая, шестая ячейка будут отвечать за хранение значения счётчиков для передачи из в PS.
Карта памяти получается следующая:

В регистрах управления состоянием счетчика и сброса каждый отдельный бит будет отвечать за соответствующий номеру бита счётчику:

Таким образом максимальное количество счётчиков которое можно сделать при таком планировании расходования памяти - 32 штуки.
Для хранения и промежуточной обработки данных объявим два регистра для хранения 32-битных данных о состоянии сброса и состояния счетчиков. Присвоим их соответствующим выходам нашего модуля. Запишем это в файл counter_mgmt.v:
reg [31:0] cnt_rst; reg [31:0] cnt_en; assign cnt_0_rst = cnt_rst[0]; assign cnt_1_rst = cnt_rst[1]; assign cnt_2_rst = cnt_rst[2]; assign cnt_0_en = cnt_en[0]; assign cnt_1_en = cnt_en[1]; assign cnt_2_en = cnt_en[2];
Следующим шагом нужно определиться с набором команд, которые мы будем посылать на исполнение. Тут всё достаточно очевидно:

После этого можно в целом ввести соответствующие режимы в которых будет находиться данный модуль:
localparam IDLE = 4'd1; localparam EN = 4'd2; localparam EN_W = 4'd3; localparam EN_R = 4'd4; localparam RST = 4'd5; localparam RST_W = 4'd6; localparam RST_R = 4'd7; localparam WRT = 4'd8; localparam WRT_W = 4'd9; localparam WRT_R = 4'd10;
Так же добавим регистр для хранения текущего значения состояния автомата:
reg [3:0] state = IDLE;
Определим оставшиеся сигналы и регистры которые нам пригодятся при работе с памятью BRAM.
reg we_r = 1'd0; // Регистр для сигнализирования о том что будет производиться запись assign we = we_r; // Присвоение значения регистра выходному сигналу reg [31:0] addr_r = 32'd0; // Регистр для передачи адреса для записи\чтения assign addr = addr_r; // Присвоение значения регистра выходному сигналу reg [31:0] dout_r = 32'd0; // Регистр для хранения данных передаваемых в BRAM assign dout = dout_r; // Присвоение значения регистра выходному сигналу
Добавим еще регистр, который будет использован для последовательной записи значений из каждого счетчика:
reg [3:0] reg_choose = 4'h0;
Опишем, что должно происходить c Write Enable и Address при попадании автомата в тот или иной state:
always @(posedge clk_i) begin case(state) IDLE: begin // Пока состояние IDLE читаем адрес 0x0 we_r
Теперь нам нужно сделать механизм который будет в цикле опрашивать BRAM на предмет наличия той или иной команды на входе и определять текущий state автомата:
always @(posedge clk_i) begin if (rst_i == 1'b1) begin state
И для передачи данных сделаем еще один behavioral-блок для того, чтобы передавать данные:
always @(posedge clk_i) begin case(state) EN_R: begin cnt_en[0]
Полный исходный код модуля доступен тут.
После этого можно подключить к ключевым выходам обмена логический анализатор. Им мы сможем воспользоваться когда будет активна PS-система. Добавить логический анализатор можно путем добавление IP-блока Integrated Logic Analyzer.
Подключаем тактирование ILA к FCLK_CLK1. И открываем настройки лог. анализатора. Выбираем Monitor Type как Native. Записываем Number of Probes в значение 13 штук.

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

К слову. Наличие логического анализатора достаточно сильно увеличивает время синтеза всей схемы в целом. Поэтому если вы уверены в работоспособности своего автомата - логический анализатор можно не добавлять. Ну а поскольку я начинающий и не верю что всё заработает с первого раза - я соединю все линии связанные с управлением счётчиком с лог. анализаторам и пойду заваривать кофе, пока идет синтез.
На будущее отмечу общую последовательность работы с лог.анализатором:
- Заливаем из Hardware manager в Vivado bitstream-файл в ПЛИС;
- Открываем SDK и делаем Clean Project;
- Запускаем приложение (в случае если мы отлаживаемся в baremetal-приложении);
- Нажимаем кнопку Refresh device и откроется логический анализатор;
- Расставляем нужные триггеры на нужных линиях и наблюдаем за результатом;
Попробуйте подебажить самостоятельно. Там ничего сложного нет.
Проверим, что получилось
Теперь можно подготовить проект к синтезу и имплементации с генерацией bitstream-файла.
- На основном поле дизайна нажимаем Validate Design и проверяем, всё ли хорошо.
- В левом меню Hierarchy правой кнопкой кликаем на файле zynq.bd и делаем команду Create HDL Wrapper, выбираем Let Vivado manage wrapper and auto-update и нажимаем ОК.
- После нажимаем команду в левой части окна Generate Block Design, выбираем Synthesis Options - Global и нажимаем Generate. Дожидаемся конца генерации и запускаем синтез через команду Generate bitstream.
Процедура синтеза будет достаточно длительной (около 10 минут).
После окончания синтеза можно экспортировать результат в SDK и протестировать результат. Сначала мы попробуем проверить проект из baremetal-приложения и загрузим всё через JTAG. Отключаем питание платы, и вытащим microSD-карту.
Нажимаем меню File - Export - Export Hardware. Ставим галочку Include bitstream. И после окончания импорта запускаем SDK через меню File - Launch SDK.
После того как откроется Xilinx SDK создаем новый проект File - New - Application Project.

Пишем имя нового проекта, нажимаем Next и берем за основу шаблонный проект Hello World. После того как создан новый проект, переходим в его иерархию и находим файл helloworld.c:

И редактируем его следующим образом:
#include #include "platform.h" #include "xil_printf.h" #include "xil_io.h" #include "xparameters.h" #include "sleep.h" int main() < int num; int rev; init_platform(); // Enable all counters Xil_Out32(XPAR_BRAM_0_BASEADDR + 4, 0x7); // Регистр ENA и значение 0b111 Xil_Out32(XPAR_BRAM_0_BASEADDR + 0, 0x1); // Команда ENA xil_printf("Enable all counters \n\r"); Xil_Out32(XPAR_BRAM_0_BASEADDR + 8, 0x7); // Регистр RST и значение 0b111 Xil_Out32(XPAR_BRAM_0_BASEADDR + 0, 0x2); // Команда RST xil_printf("Counting. \n\r"); while(1) < Xil_Out32(XPAR_BRAM_0_BASEADDR + 0, 0x3); // Записываем в память текущие значения for(num = 0; num < 6 ; num++) < rev = Xil_In32(XPAR_BRAM_0_BASEADDR + num * 4); xil_printf("The data at 0x%x is 0x%x \n\r", XPAR_BRAM_0_BASEADDR + num * 4, rev); >xil_printf("\n\r"); usleep(500000); > cleanup_platform(); return 0; >
Исходный код можно взять тут.
Если описать то, что делает этот код то получается следующий алгоритм:
- Инициализируем платформу;
- Записываем новые значения в регистр ENA, чтобы включились первые три счётчика;
- Отправляем команду ENA в регистр команд;
- Записываем новые значения в регистр RST, чтобы был отключен сигнал сброса на трех счётчиках (0 - сброс активен, 1 - не активен);
- Отправляем команду RST в регистр команд;
- Теперь наши счетчики могут считать входящие импульсы и мы можем проводить чтение. Делаем это чтение с помощью команды WRT в цикле;
- Выводим значения ячеек памяти в цикле;
После загружаем bitstream в FPGA через меню Xilinx - Program FPGA и нажимаем Program.

После можно стартовать проект. Кликаем правой кнопкой по проекту, выбираем пункт Run as - Launch on hardware (System debugger).
Если подать импульсы на ножки, которые мы указали в constraints-файле - то счетчики будут нарастать. Проверить это можно если открыть консоль minicom и понаблюдать за выводом:
minicom -D /dev/ttyUSBx
Бесконечно будут сыпаться значения наших регистров:
The data at 0x40000000 is 0x0 - текущее значение регистра команд The data at 0x40000004 is 0x7 - регистр вкл\выкл счётчиков The data at 0x40000008 is 0x7 - регистр сброса счётчиков The data at 0x4000000C is 0x14 - значение первого счётчика The data at 0x40000010 is 0x1E - значение второго счётчика The data at 0x40000014 is 0x18 - значение третьего счётчика
Отлично, в baremetal работает. Можно попробовать подергать импульсы из Linux.
Проверка из Linux
На этом этапе можем считать, что наша основная часть работы выполнена и можно проверить работает ли такое же взаимодействие как в baremetal из Linux. Тут всё достаточно просто, читать данные и управлять счетчиками мы будем с помощью простой userspace-программы на Си.
Для этого нам нужно:
- Пересобрать образ BOOT.BIN собранный в прошлых занятиях с новым bitstream-файлом собранным в этом проекте и залить его на SD-карту заменив предыдущий. Прочитать об этом можно в этой статье, а готовый BOOT.BIN и все остальные файлы можно тут;
- Установить кросс-компилятор и откомпилировать исходный файл нашей программы для Linux;
- Загрузить программу на плату и запустить её;
- Включив счетчики, отключив сигнал сброса, послать определенное количество импульсов и убедиться, что всё работает как задумано изначально;
Перейдем к реализации намеченного. Устанавливаем кросс-компилятор:
sudo apt install gcc-arm-linux-gnueabihf
Далее нам необходимо создать файл который мы будем компилировать и потом запускать на Zynq. В папке с проектом я создаю папку Application:
megalloid@megalloid-lenovo:~$ cd Zynq/Projects/10.LinuxMulticounter/ megalloid@megalloid-lenovo:~/Zynq/Projects/10.LinuxMulticounter$ mkdir Application megalloid@megalloid-lenovo:~/Zynq/Projects/10.LinuxMulticounter$ cd Application/
В этой папке я создаю два файла, первый файл исходного кода counter_mgmt.c и Make-файл для удобной кросс-компиляции:
megalloid@megalloid-lenovo:~/Zynq/Projects/10.LinuxMulticounter/Application$ touch counter_mgmt.c Makefile
В файл counter_mgmt.c пишем следующее содержимое:
#include #include #include #include #include #include #include #define BRAM_CTRL_0 0x40000000 #define DATA_LEN 6 int fd; unsigned int *map_base0; int sigintHandler(int sig_num) < printf("\n Terminating using Ctrl+C \n"); fflush(stdout); close(fd); munmap(map_base0, DATA_LEN); return 0; >int main(int argc, char **argv) < signal(SIGINT, sigintHandler); fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) < printf("can not open /dev/mem \n"); return (-1); >printf("/dev/mem is open \n"); map_base0 = mmap(NULL, DATA_LEN * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BRAM_CTRL_0); if (map_base0 == 0) < printf("NULL pointer\n"); >else < printf("mmap successful\n"); >unsigned long addr; unsigned int content; int i = 0; addr = (unsigned long)(map_base0 + 1); content = 0x7; map_base0[1] = content; printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content); addr = (unsigned long)(map_base0 + 0); content = 0x1; map_base0[0] = content; printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content); addr = (unsigned long)(map_base0 + 2); content = 0x7; map_base0[2] = content; printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content); addr = (unsigned long)(map_base0 + 0); content = 0x2; map_base0[0] = content; printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content); while(1) < addr = (unsigned long)(map_base0 + 0); content = 0x3; map_base0[0] = content; printf("%2dth data, address: 0x%lx data_write: 0x%x\t\t\n", i, addr, content); sleep(1); printf("\nread data from bram\n"); for (i = 0; i < DATA_LEN; i++) < addr = (unsigned long)(map_base0 + i); content = map_base0[i]; printf("%2dth data, address: 0x%lx data_read: 0x%x\t\t\n", i, addr, content); >> >
Ссылка на исходный код.
После редактирования сохраняем файл и открываем файл Makefile. В него записываем следующее:
CC=arm-linux-gnueabihf-gcc CFLAGS ?= -O2 -static objects = counter_mgmt.o CHECKFLAGS = -Wall -Wuninitialized -Wundef override CFLAGS := $(CHECKFLAGS) $(CFLAGS) progs = counter_mgmt counter_mgmt: $(objects) $(CC) $(CFLAGS) -o $@ $(objects) clean: rm -f $(progs) $(objects) $(MAKE) -C clean .PHONY: clean
После этого можно запустить процесс компиляции и увидеть что у нас появился исполняемый файл:
# make # file counter_mgmt counter_mgmt: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, BuildID[sha1]=adfec945dc44cf0b8a702405ca2a2ae9af1b06dd, for GNU/Linux 3.2.0, not stripped
После этого можно загрузить файл на плату и проверить работает ли наша программа. Вариантов загрузки может быть несколько:
- Предварительно положить файлы на SD-карту и после загрузки Linux примонтировать раздел на котором она будет расположена;
- Стянуть файл по локальной сети подключив Ethernet-кабель к плате;
Первый способ достаточно прост. Скидываем через картридер откомпилированный файл на SD-карту. Открываем UART-консоль нашей отладочной платы и пишем:
# mount /dev/mmcblk0p1 /media # ls /media/ BOOT.bin counter_mgmt uImage uramdisk.image.gz system.dtb uboot.env
Второй способ тоже очень простой. Подключаем кабель Ethernet от роутера к плате. Смотрим появился ли IP-адрес:
# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 82:A4:28:8C:74:0A inet addr:192.168.2.188 Bcast:192.168.2.255 Mask:255.255.255.0 inet6 addr: fe80::2a2f:f8c1:f353:e8b/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:6 errors:0 dropped:0 overruns:0 frame:0 TX packets:13 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1404 (1.3 KiB) TX bytes:1562 (1.5 KiB) Interrupt:33 Base address:0xb000
В buildroot который мы собирали в предыдущем уроке уже настроен режим автоконфигурирования сетевого интерфейса и запущена служба DHCP для получения IP-адреса. Если у вас другой Linux - тоже присвоить IP-адрес вручную через команду:
# ifconfig eth0 192.168.2.188 netmask 255.255.255.0 up
После этого смотрим IP-адрес на компьютере, пингуем его из консоли Zynq, тем самым проверяя что связь есть и скачиваем файл по SSH (на PC должен быть включен SSH-сервер):
# ping 192.168.2.121 PING 192.168.2.121 (192.168.2.121): 56 data bytes 64 bytes from 192.168.2.121: seq=0 ttl=64 time=892.638 ms 64 bytes from 192.168.2.121: seq=1 ttl=64 time=1.057 ms 64 bytes from 192.168.2.121: seq=2 ttl=64 time=2.897 ms 64 bytes from 192.168.2.121: seq=3 ttl=64 time=2.661 ms 64 bytes from 192.168.2.121: seq=4 ttl=64 time=147.555 ms ^C --- 192.168.2.121 ping statistics --- 5 packets transmitted, 5 packets received, 0% packet loss round-trip min/avg/max = 1.057/209.361/892.638 ms # scp megalloid@192.168.2.121:/home/megalloid/Zynq/Projects/10.LinuxMulticounter/Application/counter_mgmt . Host '192.168.2.121' is not in the trusted hosts file. (ssh-ed25519 fingerprint sha1!! 78:97:ca:b2:0d:fb:49:3a:3d:d3:6a:69:a1:10:b5:92:69:95:e1:46) Do you want to continue connecting? (y/n) y counter_mgmt 100% 3490KB 3.4MB/s 00:01 # chmod +x counter_mgmt # ./counter_mgmt
Результат вывода программы должен выглядеть следующим образом:
# ./counter_mgmt /dev/mem is open mmap successful 0th data, address: 0xb6fd0004 data_write: 0x7 0th data, address: 0xb6fd0000 data_write: 0x1 0th data, address: 0xb6fd0008 data_write: 0x7 0th data, address: 0xb6fd0000 data_write: 0x2 0th data, address: 0xb6fd0000 data_write: 0x3 read data from bram 0th data, address: 0xb6fd0000 data_read: 0x0 1th data, address: 0xb6fd0004 data_read: 0x7 2th data, address: 0xb6fd0008 data_read: 0x7 3th data, address: 0xb6fd000c data_read: 0xb 4th data, address: 0xb6fd0010 data_read: 0x12 5th data, address: 0xb6fd0014 data_read: 0xa 6th data, address: 0xb6fd0000 data_write: 0x3
Видно, что у нас работают все счётчики и при подаче импульсов (в моем случае это кнопки из предыдущих уроков) - у нас наращивается значение счётчика.
Задачу можем считать решенной. Увидимся в следующих уроках! =)
P.S. Если у вас есть какие-либо интересные и не слишком сложные задачки, которые можно было бы попробовать решить с помощью Zynq - пишите мне в Telegram @megalloid. И мне будет интересно поразбираться, и возможно чем-то помогу вам. Если есть желание - можете сделать донат как скромную оплату за ту уйму времени что я потратил на подготовку этого материала.
Настройка Google Tag Manager — инструкция
Подробный обзор Google Tag Manager. От понятий и принципа работы, до настройки инструментов веб-аналитики.
- Что такое Google Tag Manager?
- Для каких целей используется Google Tag Manager?
- Основные понятия Google Tag Manager
- Как работает Google Tag Manager?
- Создание аккаунта и контейнера Google Tag Manager
- Как добавить тег в Google Tag Manager
- Как выполнить отладку в Google Tag Manager
- Версии и публикация в Google Tag Manager
- Администрирование в Google Tag Manager
- Как менялся Google Tag Manager
Что такое Google Tag Manager?
Google Tag Manager – инструмент управления тегами на сайте или в мобильном приложении. Через контейнер Google Tag Manager в удобном, интуитивно понятном интерфейсе осуществляется управления тегами таких сервисов как Google Analytics, Яндекс Метрика, Google Ads, Facebook, ВКонтакте и многих других. Вы можете выполнять отладку без внесения изменений в исходный код на сайте, оперативно добавлять или отключать теги. При необходимости настройки Google Tag Manager другим специалистом с помощью делегирования прав доступа можно предоставить соответствующий уровень доступа.
Для каких целей используется Google Tag Manager?
С его помощью внедряют на сайте сервисы веб-аналитики (например, Google Analytics, Яндекс Метрика), настраивают пиксели для сбора данных по аудиториям (Facebook, ВКонтакте), запускают тестирования (Google Optimize), а также любые другие системы, для интеграции которых необходимо в исходном коде страниц сайта разместить код JavaScript.
Преимущества Google Tag Manager
Снижение зависимости от разработчиков (можно самостоятельно выполнять большое количество операций по настройке), оперативность решения задач, единое пространство управления тегами, возможность сохранить состояние контейнера в версии и позже вернуться к нему (версионность).
Недостатки Google Tag Manager
В некоторых случаях сильная зависимость от исходного кода страниц сайта. При изменении структуры страницы или свойств отдельного элемента могут перестать работать ранее выполненные настройки (эта проблема может быть решена).
Новые кейсы, инструкции публикуются в Telegram канале, подпишитесь сейчас!
Основные понятия Google Tag Manager
Контейнер — можно разделить на два блока: технический и то, где непосредственно происходит работа. Первый — это код JavaScript, подлежащий размещению на всех страницах сайта, чтобы Google Tag Manager стал доступен на нем. Второй — рабочее пространство, в котором вы управляете тегами, триггерами и переменными.
Тег — фрагмент кода на языке JavaScript. Примеры: код счетчика Google Analytics, пиксель Facebook и т.п.
Триггер — условие, при котором тег активируется (выполняется). Все триггеры базируются на событиях (клики мешью, просмотры страниц, отправки форм и т.п.), а также в них могут быть заданы дополнительные условия. Например, клик по кнопке «В корзину» (действие — клик , условие — текст кнопки В корзину )
Переменная — обхект, который в зависимости от действия на сайте принимает некоторое значение. Например, url адрес текущей страницы, текст или идентификатор кнопки.
Уровень данных — переменная JavaScript, через которую в Google Tag Manager можно передать необходимую информацию, а при желании и извлечь из него. Имя переменной для уровня данных dataLayer .
Как работает Google Tag Manager?
После создания контейнера Google Tag Manager вам предложат разместить на своем сайте специальный код, который добавляет диспетчер тегов на страницы вашего сайта. В процессе загрузки страницы контейнер загружает теги, триггеры и переменные, которые вы создали в нем. Загрузка происходит асинхронно, очень быстро и как правило не влияет скорость загрузки страницы (если только в вашем Google Tag Manager не слишком много тегов, триггеров и переменных). После загрузки контейнер фиксирует события, которые происходят на сайте. Происходит проверка — какие триггеры связаны с этими событиями, если таковые есть происходит дополнительная проверка условий в триггерах и если все условия соблюдены то запускаются теги, связанные с этим триггером. Тег выполняет необходимый код и информация отправляется в соответствующую систему, например, Google Analytics.
Создание аккаунта и контейнера Google Tag Manager
Давайте приступим к установке этого удобного инструмента на свой сайт. Для начала перейдите на главную страницу проекта расположенную по адресу https://marketingplatform.google.com/about/tag-manager/ , если у вас уже есть учетная запись на Google пройдите по кнопке Sign in to Tag Manager в правом верхнем углу, если нет, нажмите кнопку Start for free и завершите несложную процедуру создания аккаунта.

Главная страница Google Tag Manager
Итак, вы вошли в Google Tag Manager и вам предлагают создать аккаунт. Обратите внимание на подсказки, которые появляются практически на каждой странице, не пренебрегайте ими, внимательно читайте и принимайте к сведению информацию, это позволит вам быстрее изучить новый инструмент и узнать о его возможностях.
Создание аккаунта состоит их двух этапов. На первом этапе необходимо ввести название. Выбирайте его осмысленно, чтобы в том случае, когда аккаунтов станет много вы смогли быстро найти нужный. На втором этапе необходимо указать имя контейнера, выбрать вариант его использования на страницах сайта или в мобильном приложении.

Создание аккаунта и контейнера Google Tag Manager
Я рекомендую давать название аккаунту и контейнеру идентичное доменному имени сайта.
После того, как все необходимые поля заполнены нажмите на кнопку Создать , вам предложат ознакомиться с «Соглашением об Условиях использования Диспетчера тегов Google». Внимательно прочтите его и сделайте выбор принять его или нет.
Если вы согласились с условиями использования Google Tag Manager для вас будет сгенерирован код, который необходимо разместить на всех страницах вашего сайта сразу после открывающего тега . Если вы сами занимались разработкой сайта, сложностей у вас не возникнет, во всех других случаях обратитесь к специалистам, которые могут провести эту процедуру за несколько минут. Обратите внимание, что внесение изменений в исходный код страниц сайта может привести к неработоспособности сайта, будьте внимательны.

Код контейнера Google Tag Manager
На этом создание аккаунта и установка Google Tag Manager на сайт завершены. Можно приступать к первым настройкам, обычно это добавление тега Google Analytics и Яндекс Метрики.
Как добавить тег в Google Tag Manager
Добавить любой тег в Google Tag Manager можно несколькими способами. Первый через раздел управления тегами:

Добавление тега в Google Tag Manager
Второй, с главной страницы контейнера:

Главная страница контейнера
Внедрим Google Analytics и Яндекс Метрику через Google Tag Manager. Перейдем к настройкам счетчика Google Analytics и скопируем идентификатор счетчика:

Номер счетчика Google Analytics
После этого, специально для Googlee Analytics создаем переменную, которая будет хранить в себе необходимые настройки. Переходим к Переменные , в блоке Пользовательские переменные выбираем Создать , в выпадающем списке ищем Настройки Google Analytics :

Переменная настройки Google Analytics
В открывшемся окне в поле Идентификатор отслеживания вставляем номер счетчика и сохраняем переменную:

После этого создаем тега типа Google Analytics — Universal Analytics со следующими настройками:

Первый тег Google Analytics
Теперь добавим Яндекс Метрику. Перейдите в настройки счетчика Яндекс Метрики и скопируйте код счетчика. После этого повторите действия по созданию тега, только в качестве типа укажите Пользовательский HTML . В содержимое тега вставьте код счетчика Метрики, добавьте триггер All Pages и сохраните тег:

Тег Яндекс Метрики
После описанных действий мы получаем два тега, которые внедряют на сайт через Google Tag Manager такие сервисы как Google Analytics и Яндекс Метрику:

Яндекс Метрика и Google Analytics в Google Tag Manager
При необходимости аналогичные действия выполняются и для других систем.
Как выполнить отладку в Google Tag Manager
Теги добавлены, нужно проверить насколько корректно выполнены настройки. Сделать это можно через отладчик Google Tag Manager. Запустим его нажав на Предварительный просмотр в правом верхнем углу. После запуска отладчика в контейнере появляется информационное сообщение, что вы находитесь в режиме предварительного просмотра:

Запуск отладчика Google Tag Manager
Переходим на сайт, обновляем страницу, в нижней части появляется блок с отладочной информацией. Выбрав слева Summary получаем сведения о всех активированных тегах. Поскольку мы создали только два, то они должны быть в списке:

Информация в отладчике
Отлично! Все работает. Но проверим еще в отчетах в режиме реального времени Google Analytics:

Проверка в реальном времени
Google Analytics получил отправленных хит с сайта, выполненные настройки в Google Tag Manager корректны. Для Яндекс Метрики такого отчета нет, но можно использовать дополнительный параметр _ym_debug=1 в URL текущей страницы, открыть инструменты разработчика в браузере, перейти в консоль и получить подтверждение отправки обращения в Метрику.
Версии и публикация в Google Tag Manager
Проверили — все работает, но пока эти изменения доступны только нам в режиме предварительного просмотра или отладки. Сделаем их доступными для всех, опубликовав контейнер Google Tag Manager. В правом верхнем углу выбираем Отправить и видим следующее окно:

Публикация контейнера Google Tag Manager
Теперь у нас есть выбор опубликовать изменения и сохранить версию, либо только создать версию. В чем отличие?
- Публикация и создание версии — сохраняется текущее состояние контейнера и становится доступно всем посетителям сайта
- Новая версия — сохраняется текущее состояние контейнера
Со временем в вашем контейнере появится ни одна версия. Если по какой=то причине придется вернуться к ранее выполненным настройкам отменив все изменения после нее, то вы можете в списке выбрать необходимую версию и опубликовать ее, т.е. вернуться к ней. При желании можно одну из ранних версий сделать актуальной и работать в ней.
Администрирование в Google Tag Manager
Перед вами встала задача выполнить некоторые действия в контейнере, но ваших знаний пока не достаточно для этого. Что можно предпринять? Найти специалиста, который знает как справиться с задачей, делегировать ему права на работу в контейнере, получить результат, отозвать доступ. Делается это с помощью соответствующих опций в настройках аккаунта и контейнера Google Tag Manager.
Настроить доступ в Google Tag Manager можно достаточно гибко, но самое важное я не рекомендую выдавать на уровне аккаунта права администратора!

Управление доступом в Google Tag Manager
Представьте себе задачу — перенести настройки из одного контейнера в другой. Выполнять вручную аналогичную конфигурацию не самое приятное занятие, особенно если выполнено много различных настроек в Google Tag Manager, выручает возможность импорта\экспорта в Google Tag Manager:

Импорт контейнера Google Tag Manager
Соответствующая опция доступна на уровне настроек контейнера.
Как менялся Google Tag Manager
Я давно работаю с Google Tag Manager, этот блог я начал вести в 2013 году. С того времени у меня собралась коллекция скриншотов с интерфейсом Google Tag Manager, давайте вместе вспомним как развивался этот инструмент.
Таким был интерфейс при создании контейнера:

Теги, триггеры и переменные раньше назывались Теги, правила, макросы :

Примерно так происходило добавление нового тега:

Таким было окно, в котором происходила конфигурация тега:

Создание нового тега происходило по следующей схеме:

Запуск отладчика происходил иначе:

Типы тегов и выбор необходимого происходил на первом шаге конфигурирования тега:

Конфигурация тега Google Analytics выполнялась согласно следующего порядка:

Фрагмент из отладчика:

Интерфейс изменился на текущий, после этого в нем происходили некоторые изменения, но пока серьезных не было:








