Язык SCL (часть-2) — АСУ ТП: сообщество инженеров

Язык SCL (часть-2)

Генератор программного UART

Большинство контроллеров PIC младшего и среднего семейства имеют на борту всего один аппаратный USART-модуль, но часто бывает нужно управлять двумя и более устройствами посредством этого интерфейса. При отладке программы хорошо бы убедиться в том, что прораммный UART-приемник работает корректно. Вот пример скрипта, который генерирует на вход ПИКу сигнал UART. Скорость выбирается пользователем (по умолчанию 9600), данные берутся из файла.

Пример исходного файла data_uart.txt:

baudrate 19200
wait 1 ms
10 20 30 40 50 60
wait 3 ms
55 66 77 88

Здесь baudrate — команда на изменение скорости, wait — команда на выдержку паузы. Числа даны в десятичной системе счисления (как их задавать в шестнадцатеричной или в символьном виде, — не знаю).

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

  1. байт может отправляется из разных мест процесса;
  2. байт может отправляться из разных процессов;
  3. во время отправки данных головной процесс должен заниматься своими делами.

Для синхронизации служит общая переменная UART_Work. Когда она = 0, процесс отправки байта свободен. Для отправки байта головной процесс должен подготовить данные для отправки (записать нужное значение в UART_Data), а потом установить UART_Work в «1», тем самым сообщив процессу отправки, что можно начинать работать. Процесс отправки сам обнулит эту переменную, когда байт будет полностью передан, сообщем тем самым основному процессу, что передатчик вновь свободен.

-------------------------------------------------------------------------------
--
-- Данный скрипт демонстрирует формирование программного UART сигнала на входе
-- контроллера PORTC, 5. Данные читаются из файла "data_uart.txt"
--
-------------------------------------------------------------------------------
-- Пример файла с данными:
-------------------------------------------------------------------------------
--  baudrate 19200
--  wait 1 ms
--  10 20 30 40 50 60
--  wait 3 ms
--  55 66 77 88
-------------------------------------------------------------------------------
 
testbench for "pic16f877a" is
begin
 
 
process is
begin
    report "======================== SCL loaded OK! ==========================";
    wait;
end process;
 
---------------------------------------------------
 
uart_send_byte: process is              -- Подпрограмма вывода одного байта по последовательному
                                        -- интерфейсу UART через вход RC5
 
    variable UART_Delay   : time;       -- Длительность одного бита
    variable UART_Data    : byte;       -- Передаваемый байт
    variable UART_Bit     : byte;       -- Для выделения бита при передаче
    variable UART_Counter : integer;    -- Счетчик битов
    variable UART_Work    : integer;    -- Установкой этой переменной в 1 из другого
                                        -- процесса мы инициируем начало передачи.
 
begin
 
    RC5 <= '1';
    UART_Work := 0;                     -- Сообщаем головной программе, что процесс свободен,
                                        -- моно передавать новый байт
 
    while UART_Work == 0 loop           -- Ожидаем команду на старт от основного процесса
        wait for 1 ic;
    end loop;
 
    RC5 <= '0';                         -- Формируем стартовый бит
    wait for UART_Delay;
 
    UART_Counter := 8;                  -- цикл для 8 бит данных
 
    while UART_Counter > 0 loop
 
        UART_Counter := UART_Counter - 1;
        UART_Bit := UART_Data / 2;      -- Выделяем младший бит
        UART_Bit := UART_Data - UART_Bit * 2;
        UART_Data := UART_Data / 2;
 
        if UART_Bit == 0 then           -- Отправляем его
            RC5 <= '0';
        else
            RC5 <= '1';
        end if;
 
        wait for UART_Delay;
 
    end loop;
 
    RC5 <= '1';                         -- Формируем стоповый бит
    wait for UART_Delay;
 
end process uart_send_byte;
 
---------------------------------------------------
 
uart: process is                            -- Читаем данные из файла и формируем
                                            -- из них сигнал для контроллера
 
    file     InFile : text;                 -- Рабочий файл
    variable Status : file_open_status;
    variable InLine : line;                 -- Строка, прочитанная из файла
    variable i      : integer;              -- Рабочая переменная
    variable Delay  : time;                 -- Рабочая задержка
 
begin
 
    UART_Delay := 1 sec / 9600;             -- скорость по умолчанию 9600
 
                                            -- Открываем файл с данными
    file_open(Status, InFile, "data_uart.txt", read_mode);
    if Status != open_ok then
        report "Ошибка открытия файла data_uart.txt" severity failure;
    end if;
 
 
    while endfile(InFile) == false loop     -- Обрабатываем, пока файл не пуст
 
        readline(InFile, InLine);
        print("Прочитана строка: ", InLine);
 
        while match(InLine, "") == false loop
 
            -- Проверяем строку на наличие команды
 
            if match(InLine, "baudrate") == true then   -- Изменение скорости
 
                read(InLine, i);            -- Удаляем из строки слово "baudrate"
                read(InLine, i);            -- Читаем значение скорости
 
                if i != 0 then
                    UART_Delay := 1 sec / i;            -- Устанавливаем новую скорость
                    print("Новая скорость UART = ", i);
                else
                    print("ОШИБКА в файле: Скорость не может быть = 0!");
                end if;
 
            elsif match(InLine, "wait") == true then    -- Задержка
 
                read(InLine, i);            -- Удаляем из строки слово "wait"
                read(InLine, Delay);        -- Читаем значение задержки
                print("Ждем ", Delay);
                wait for Delay;             -- формируем задержку
 
            else                                        -- Передача байта
 
                read(InLine, UART_Data);                -- Читаем байт
                print("Отправка байта ", UART_Data);
                UART_Work := 1;                         -- Даем команду на начало передачи
 
                while UART_Work == 1 loop               -- Ожидаем завершения передачи
                    wait for 1 ic;
                end loop;
 
            end if;
 
        end loop; -- match InLine
 
    end loop; -- endfile
 
    file_close(InFile);
 
end process uart;
 
end testbench;

Ниже приведен график генерируемого сигнала. На нем отчетливо видно, что данные передаются двумя пакетами с интервалом 3 мс.

Генератор аналогового сигнала

Симулятор в интегрированной среде MPLAB IDE не позволяет имитировать разные уровни напряжения на аналоговых входах. Однако он позволяет симулировать регистры ADRESH и ADRESL напрямую. Этим мы можем воспользоваться для имитации аналоговых сигналов.

Ниже приведен пример, который будет заполнять регистры ADRESH:ADRESL значениями из файла. Это будет происходить в тот момент, когда программа будет обращаться к АЦП. Это пример является только демонстрацией необходимых действия для формирования регитсров ADRES, он не подходит для практического применения, поскольку входные данные синхронизированы только с обращениями программы к АЦП. Т.е. если обращений не будет, то и сигналы формироваться не будут (или другими словами: когда бы мы не обратились к АЦП, мы всегда прочитаем один и тот же сигнал). На практике такое не встречается.

testbench for "pic16f877a" is
begin
 
process is
begin
    report "======================== SCL loaded OK! ==========================";
    wait;
end process;
 
---------------------------------------------------
 
ADC: process is
 
    file     InFile : text;                     -- Рабочий файл
    variable Status : file_open_status;
    variable InLine : line;                     -- Строка, прочитанная из файла
    variable Value  : integer;                  -- Рабочая переменная
 
    constant FileName : string := "data_adc.txt";
 
begin
 
    file_open(Status, InFile, FileName, read_mode);
 
    if Status != open_ok then
        report "Ошибка открытия файла!" severity failure;
    end if;
 
 
    while endfile(InFile) == false loop         -- Обрабатываем, пока файл не пуст
 
        readline(InFile, InLine);
        print("Прочитана строка: ", InLine);
 
        while match(InLine, "") == false loop   -- Обрабатываем строку, пока она не пуста
 
            wait until ADCON0.GO_nDONE == '1';  -- Ждем, когда программа начнет чтение ADC
            read(InLine, Value);
 
            if ADCON0.ADON == '1' then
 
                print("Формируем значение ", Value);
 
                while ADCON0.GO_nDONE == '1' loop -- Ждем завершения измерения (для 877a
                    wait for 1 ic;                -- этот бит сбрасывается MPLAB SIM'ом
                end loop;                         -- автоматически. Но, мо-моему, для неко-
                                                  -- торых контроллеров эту нужно делать
                                                  -- вручную)
 
                if ADCON1.ADFM == '0' then        -- Левое выравнивание
 
                    ADRESH <= Value /  4;
                    ADRESL <= Value * 64;
 
                else
                                                  -- Правое выравнивание
                    ADRESH <= Value / 256;
                    ADRESL <= Value;
 
                end if;
 
            end if; -- ADON
 
        end loop; -- match InLine
 
    end loop; -- endfile
 
    file_close(InFile);
    report "Конец скрипта";
    wait;
 
end process ADC;
 
end testbench;

Генератор DTMF

Итак, как уже было сказано, для практического применения предыдущий пример не очень подходит. Представьте, что нам нужно оцифровать и распознать DTMF сигнал. Для этого нам нужно точно вымерять моменты обращения к АЦП, а если скрипт синхронизирован только с самим обращением, то он будет работать при любых условиях. В новом примере мы создадим реальную модель аналогового сигнала, по которой уже можно будет отлаживать программу.

Напомню: DTMF сигнал — это сигнал, получаемый суммированием двух синусоидальных сигналов из двух наборов частот:

  1. 697 Hz, 770 Hz, 852 Hz, 941 Hz
  2. 1209 Hz, 1336 Hz, 1477 Hz, 1633 Hz

Скрипт будет состоять из 4-х процессов:

  1. FREQ1 — генератор синуса заданной частоты
  2. FREQ2 — генератор синуса заданной частоты
  3. WORK — Управляющий — будет читать файл с данными и формировать целеуказания для FREQ1 и FREQ2
  4. ADC — независимый процесс, который при обращении пользовательской программы к АЦП будет формировать значения регистров ADRESH:ADRESL по мгновенным значениям синусов, генерируемых процессами FREQ1 и FREQ2.

(Примечание: еще добавлены пятый процесс, в который для удобства вынесены объявления глобальных переменных, и шестой, который содержит строку «SCL loaded OK!»)

Формат файла «data_dtmf.txt»

Этот файл содержит список команд для управления процессом. В самом простом случае он может состоять из чисел от 1 до 16 (0 — будет воспринят как 16), из которых будет генерироваться последовательность тонов. Однако, с помощью доолнительных команд можно менять параметры сигналов:

freq <c> <f>

Генерировать синус частоты f на канале c (c = 1 или 2)

stop

остановить генерацию (отключает оба канала)

duration <d>

задать длительность тонов (по умолчанию 100 мс)

pause <p>

задать паузу между тонами (по умолчанию 100 мс)

level

задать уровень сигнала от 0% до 100% (по умолчанию 100%)

Также допускается использование комментариев

Пример рабочего файла:

  
// Задаем временные параметры тонов
duration 30 ms
pause 30 ms

// Генерируем все тоны по очереди
1 2 3 4 5 6 7 8 

// Уменьшаем уровень сигнала втрое 
level 33

9 10 11 12 13 14 15 16

// Генерируем опорную частоту 1 КГц на первом канале
freq 1 1000
wait 100 ms
stop

// Генерируем опорную частоту 2 КГц на втором канале
freq 2 2000
wait 100 ms
stop

Вот отчет его обработки:

(0)  SIM-N0001 Note: ======================== SCL loaded OK! ==========================
(0)  SIM-N0001 Note: Line:  // Задаем временные параметры тонов
(0)  SIM-N0001 Note: Line:  duration 30 ms
(0)  SIM-N0001 Note: Установлено время гудка  30000000 ns
(0)  SIM-N0001 Note: Line:  pause 30 ms
(0)  SIM-N0001 Note: Установлено время паузы  30000000 ns
(0)  SIM-N0001 Note: Line:
(0)  SIM-N0001 Note: Line:  // Генерируем все тоны по очереди
(0)  SIM-N0001 Note: Line:  1 2 3 4 5 6 7 8
(0)  SIM-N0001 Note: Генерируется тон:  1
ADC-W0001:	Tad time is less than 1.60us
ADC-W0008: No stimulus file attached to ADRESL for A/D.
(60000)  SIM-N0001 Note: Генерируется тон:  2
(120000)  SIM-N0001 Note: Генерируется тон:  3
(180000)  SIM-N0001 Note: Генерируется тон:  4
(240000)  SIM-N0001 Note: Генерируется тон:  5
(300000)  SIM-N0001 Note: Генерируется тон:  6
(360000)  SIM-N0001 Note: Генерируется тон:  7
(420000)  SIM-N0001 Note: Генерируется тон:  8
(480000)  SIM-N0001 Note: Line:
(480000)  SIM-N0001 Note: Line:  // Уменьшаем уровень сигнала втрое
(480000)  SIM-N0001 Note: Line:  level 33
(480000)  SIM-N0001 Note: Установлен уровень сигнала =  33 %
(480000)  SIM-N0001 Note: Line:
(480000)  SIM-N0001 Note: Line:  9 10 11 12 13 14 15 16
(480000)  SIM-N0001 Note: Генерируется тон:  9
(540000)  SIM-N0001 Note: Генерируется тон:  10
(600000)  SIM-N0001 Note: Генерируется тон:  11
(660000)  SIM-N0001 Note: Генерируется тон:  12
(720000)  SIM-N0001 Note: Генерируется тон:  13
(780000)  SIM-N0001 Note: Генерируется тон:  14
(840000)  SIM-N0001 Note: Генерируется тон:  15
(900000)  SIM-N0001 Note: Генерируется тон:  16
(900000)  SIM-N0001 Note: Line:
(900000)  SIM-N0001 Note: Line:  // Генерируем опорную частоту 1 КГц на первом канале
(900000)  SIM-N0001 Note: Line:  freq 1 1000
(900000)  SIM-N0001 Note: Частота канала 1 =  1000
(900000)  SIM-N0001 Note: Sample rate =  976 ns
(900000)  SIM-N0001 Note: OK
(900000)  SIM-N0001 Note: Line:  wait 100 ms
(900000)  SIM-N0001 Note: Ждем  100000000 ns
(1000000)  SIM-N0001 Note: Line:  stop
(1000000)  SIM-N0001 Note: Line:
(1000000)  SIM-N0001 Note: Line:  // Генерируем опорную частоту 2 КГц на втором канале
(1000000)  SIM-N0001 Note: Line:  freq 2 2000
(1000000)  SIM-N0001 Note: Частота канала 2 =  2000
(1000000)  SIM-N0001 Note: OK
(1000000)  SIM-N0001 Note: Line:  wait 100 ms
(1000000)  SIM-N0001 Note: Ждем  100000000 ns
(1100000)  SIM-N0001 Note: Line:  stop
(1100000)  SIM-N0001 Note: Line:
(1100000)  SIM-F0001 Failure: Работа скрипта успешно завершена

Текст скрипта

Наконец, сам скрипт:

(файл с таблицей синуса прилагается sinus.rar)

Скрипт смотреть в источнике.

Он, хоть и сравнительно большой, но понятный. Не составит труда дабавить еще один процесс, который будет подмешивать к сигналу шум. Для этого нужно будет воспользоваться функцией random_time, а в процессе ADC вычислять сумму не двух сигналов, а трех. Уровень шума удобно будет регулировать через входной файл.

Ниже приведен фрагмент графика сигнала, сформированного данным скриптом для команды «1» (697 Гц + 1209 Гц):

Заключение

Итак, в статье были описаны известные мне конструкции языка SCL, а также приведены несколько небольших примеров использования этого языка для имитации цифровых и аналоговых сигналов в симуляторе. Я надеюсь, что описанное здесь поможет упростить процесс отладки, а главное — поможет вам утвердиться во мнении, что симулятор в MPLAB IDE — это действительно мощное средство, и не такое оно неудобное, как может показаться, если пользоваться только диалоговым окном Stimulus, пренебрегая самим языком SCL. Так что теперь, я думаю, такие задачи, как имитация 128-битного манчестерского кода, трудностей больше не вызовут.

Тем, кто раньше пользовался диалоговым окном Stimulus для задания входных сигналов, а после прочтения статьи задумался: «А как бы мне то же самое сделать на SCL?» — напомню, что в MPLAB IDE есть возможность сформировать файл SCL для заданных в диалоговом окне сигналов. Для этого нужно в диалоговом окне «Stimulus» нажать кнопку «Advanced…» и в открывшемся окне нажать кнопку «Generate SCL File». Все заданные вами воздействия будут автоматически переведены в скрипт на языке SCL.

Источник: http://pic24.ru/doku.php/osa/articles/scl

Больше интересных и полезных статей на нашем форуме по АСУ ТПhttps://p4b.su/forums
Тендера фриланс по АСУ ТП https://p4b.su/projects
Библиотека с технической литературой по промышленной автоматизации https://p4b.su/library

Группа ВК https://vk.com/p4b_su

🔗 Язык SCL (часть-2)

Оставьте комментарий

Войти

Зарегистрироваться

Сбросить пароль

Пожалуйста, введите ваше имя пользователя или эл. адрес, вы получите письмо со ссылкой для сброса пароля.

Прокрутить вверх