Программирование DSP Изменение тамера прерывания VDK по умолчанию Mon, September 27 2021  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Изменение тамера прерывания VDK по умолчанию Печать
Добавил(а) microsin   

Один из недостатков VDK для Blackfin - слишком высокий приоритет у обработчика прерывания таймера (формирование тиков ядра, в которых запускается планировщик). Из-за этого не получается без DMA и аппаратуры реализовать критичные по точности, малые интервалы времени с помощью прерываний - например формирование DDS с помощью ШИМ по таймеру. Также отсутствуют механизмы встраивания своего кода в обработчик прерывания таймера VDK. Поэтому встает вопрос - как поменять (снизить) приоритет вызова планировщика, или как можно вызывать планировщик из своего обработчика прерывания?

В свойствах проекта на закладке Kernel в разделе System есть заманчивая опция Timer Interrupt, которая по умолчанию установлена в значение EVT_IVTMR, что соответствует обработчику прерываний системного таймера ядра с группой приоритета IVG6. В выпадающем списке этой опции можно выбрать другие варианты - None, EVT_IVG7, EVT_IVG9, EVT_IVG10, EVT_IVG11, EVT_IVG12, EVT_IVG13, EVT_IVHW, EVT_NMI. Но никакой подробной документации, как эту опцию можно использовать и никаких примеров я не нашел. К счастью, помог пост в службе поддержки analog.com [1], что позволило немного прояснить ситуацию настройкой опции Timer Interrupt. Перевод диалога в этом посту приведен ниже во врезке.

Peter847. Я использую ADSP-BF534 400MHz, и пытаюсь запустить приложение VDK с использованием обычного таймера вместо Timer Interrupt EVT_IVTMR по умолчанию, который использует таймер ядра. Например, хочу использовать EVT_IVG12 для прерывания таймера TIMER2. Для этой цели была создана простая тестовая программа, которая дергает ножкой порта в главном цикле цикле метода Run загружаемого потока VDK (boot thread). EVT_IVTMR по умолчанию таймера был заменен на EVT_IVG12, и Timer2 инициализировался в начале метода Run (до входа в бесконечный цикл потока).

Программа работает только до первого прерывания, и затем останавливается. С помощью эмулятора ICE-100B я выяснил, что прерывание передает управление в функцию _tmk_TimerISR, и постоянно вызывается снова и снова, не давая времени выполниться главному циклу загружаемого потока, т. е. он не выполняется. Очевидно, что это происходит потому, что флаг прерывания Timer2 не очищается в _tmk_TimerISR, как это необходимо делать в обычном ISR прерывания EVT_IVG12. Написанный мною ISR для Timer2 работает корректно, потому что делает очистку флага таймера оператором C-кода "*pTIMER_STATUS = 0x04;".

Есть ли какой-нибудь способ, чтобы _tmk_TimerISR очищал этот флаг Timer2?

SFernandez. Самый лучший способ получить то, то Вы хотите - вместо вызова _tmk_TimerISR напрямую в EVT вашего таймера, необходимо написать свою собственную функцию ISR на ассемблере, которая будет делать следующее:

МойISR:
   очистка_флага_таймера;
   jump _tmk_TimerISR
End:

Обратите внимание, что нельзя портить никакие регистры в этом ISR, и нельзя делать длинные переходы, long jump (т. е. jump.x) в ISR таймера VDK. Вместо этого нужны две функции, расположенные как можно ближе к _tmk_TimerISR, чтобы можно было использовать short jump.

Peter847. Я попытался сделать что-то похожее, но столкнулся с проблемами.

В ассемблере Blackfin я новичок, и не смог найти какую-либо инструкцию, которая могла бы очистить флаг прерывания таймера без использования хотя бы одного регистра. Вероятно, для сохранения и восстановления регистров нужно использовать пары инструкций [--SP] = Rx; / Rx = [SP++]; перед и после операции очистки бита?

Функция _tmk_TimerISR в настоящий момент находится внутри библиотек VDK. Какая есть возможность разместить её код в определенном месте адресного пространства, чтобы рядом с ней была расположена моя функция (выполнение условия short jump)?

Вот мой код, который компилируется без ошибок:

.section/doubleany L1_code;
 
.extern _tmk_TimerISR
.GLOBAL SystemTimer
 
SystemTimer:
   // Здесь очищается флаг прерывания таймера.
   ...
   jump _tmk_TimerISR;
.SystemTimer.end:

Единственное место, где в коде проекта упоминается _tmk_TimerISR, это автоматически создаваемый модуль VDK.cpp. Неужели мне придется восстанавливать содержимое VDK.cpp после того, как я внесу в него модификации? В качестве альтернативы этому я мог бы оставить все как есть, чтобы VDK по-прежнему использовал _tmk_TimerISR в качестве обработчика таймера, и уже во время выполнения программы подменить соответствующую запись в the Core Event Vector Table, чтобы там была запись моей функции. Это выглядит довольно неуклюже, но вероятно должно сработать, что позволит мне не тратить лишнее время на отмену автоматических изменений, которые VDSP делает каждый раз в модуле VDK.cpp.

Последний вопрос: чтобы VDK.cpp компилировался с моей функцией ISR, мне необходимо заменить _tmk_TimerISR на SystemTimer в следующей строке VDK.cpp, верно?

INT_ASM_ROUT(_tmk_TimerISR)

Я попробовал внести такое изменение, VDK.cpp компилируется, но проект все еще не проходит линковку, жалуясь на то, что не может обнаружить мою функцию SystemTimer. Я где-то пропустил декларацию SystemTimer?

SFernandez. Добавленная Вами функция на ассемблере это как раз то, что нужно. Чтобы линковщик мог найти имя функции, можете всегда добавлять нижнее подчеркивание _ к имени функции:

_SystemTimer:
   // Сохранение регистров P5 и R4:
   [--SP] = P5;
   [--SP] = R4;
 
   // Получение адреса регистра TIMER_STATUS:
   P5.l = lo(TIMER_STATUS);
   P5.h = hi(TIMER_STATUS);
   // Запись единички в бит 2, чтобы очистить флаг прерывания Timer2:
   R4 = 0x04;
   [P5] = R4;
 
   // Восстановление регистров R4 и P5:
   R4 = [SP++];
   P5 = [SP++];
 
   // Передача управления в VDK timer ISR:
   jump _tmk_TimerISR;
._SystemTimer.end:

Вы правы, когда не хотите менять модуль VDK.cpp. Любые изменения в нем будут перезаписаны, если Вы сделаете любые изменения на закладке Kernel проекта, и в некоторых случаях, когда обновлена инсталляция VDSP.

Я предполагаю следующее:

1. На закладке Kernel параметр System -> Timer Interrupt установлен "None".

2. Добавлено прерывание на закладке Kernel для EVT_IVG12 на языке ассемблера, у которого метка входа _SystemTimer, и это запрещено при загрузке (disabled at boot). Вероятно, Вы решили не добавлять файлы в GUI-интерфейсе VDSP, чтобы самостоятельно добавить свой файл в своем собственном стиле.

Если мои предположения 1 и 2 верны, то не нужно ссылаться на SystemTimer в InitializeTimer2(), потому что VDK добавит функцию в IVG12 (Вы это увидите в VDK.cpp)

По поводу короткого jump: VDK timer ISR установит атрибут prefersMem на internal, так что скорее всего код ISR появится во внутренней памяти. Если Ваш код появится во внутренней памяти, то все должно быть в порядке. Если Вы используете short jump, и дистанция для вызова получится слишком длинная, то линкер выдаст ошибку и не будет порчи регистра, чтобы Вы смогли поменять свои размещения частей кода в LDF с целью поместить две функции рядом. Вы не можете использовать любой регистр jump или даже jump.x, потому что любые используемые регистры не будут восстановлены в свое оригинальное значение.

Peter847. Для обработчика на языке ассемблера следующий код выглядит рабочим, хотя это все равно моя тестовая программа. Она использует 2 регистра так, что они сохраняются в стеке. Правильно ли я понял, что нет другого способа сбросить флаг прерывания без использования каких-либо регистров?

#include < defBF534.h>
 
.section/doubleany L1_code;
 
.extern _tmk_TimerISR
.GLOBAL SystemTimer
 
SystemTimer:
   // Сохранение регистров в стеке:
   [--SP] = P5;
   [--SP] = R4;
   
   // Здесь очищается флаг прерывания таймера:
   P5.l = lo(TIMER_STATUS);
   P5.h = hi(TIMER_STATUS);
   R4 = 0x04;
   // Запись единички в бит 2, чтобы очистить флаг прерывания Timer2:
   [P5] = R4;
 
   // Восстановление регистров из стека:
   R4 = [SP++];
   P5 = [SP++];
 
   jump _tmk_TimerISR;  // Вызов VDK timer ISR.
.SystemTimer.end:

Таймер инициализируется в начале метода Run автоматически запускаемого потока (boot thread), как показано ниже. Декларация extern разрешает линкеру найти функцию ISR на языке ассемблера, которая затем используется для перезаписи вектора IVG12 в Event Vector Table. Вероятно, декларация extern это то, что я пропустил ранее, когда линкер не мог найти мою подпрограмму на ассемблере.

extern "asm" void SystemTimer();
 
void TestMainThread::InitialiseTimer2()
{
   // Переназначение вектора прерывания IVG12:
   *pEVT12 = (void *)SystemTimer;
 
   // Инициализация таймера в режиме PWM, системный источник тактов,
   // период срабатывания 0.1 сек на тактовой частоте 133 МГц:
   *pTIMER2_CONFIG = 0x59;
   *pTIMER2_PERIOD = 13300;
   *pTIMER2_WIDTH = 0;        // PWM мы не генерируем.
   
   // Переназначить прерывание? По умолчанию IVG12.
 
   *pTIMER_ENABLE = 0x04;              // Разрешение Timer2.
 
   VDK::IMASKStruct mask = 0x00001000; // Установка разрешения IMASK для EVT12.
   VDK::SetInterruptMaskBits(mask);    // Разрешение прерывания приоритета EVT12.
 
   *pSIC_IMASK |= 0x00200000;
}

Это позволяет избежать необходимости изменять автоматически генерируемый файл VDK.cpp, что вероятно будет кошмаром для поддержки кода, когда небольшое изменение параметров Kernel приведет к перезаписи VDK.cpp и остановке работы всей системы.

Я замечаю, что инструкция JUMP.L имеет диапазон перехода приблизительно +/-16,000,000 байт, что должно устроить, если не произойдет так, например, что часть программы находится в SRAM, и остальная часть во Flash или SDRAM. Иначе я всегда мог использовать регистр P5 для хранения адреса.

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

Peter847. Благодарю, теперь все работает. Проблема была в том, что не получалось выполнить линковку без декларации extern "asm" независимо от того, что я сделал с именем ISR, но это второстепенно. Декларацию extern "asm" я оставлю.

Понимаю Вашу точку зрения по поводу jump, ведь RTI в конце _tmk_TimerISR помешает мне восстановить любой регистр, используемый для дальнего jump.

Параметр System Timer я установил в None. Это работает с установкой EVT_IVTMR, если помнить об очистке бита 7 в IMASK, но вероятно будет лучше, чтобы он изначально не был установлен.

Я устанавливал вектор прерывания в InitialiseTimer2, но также работает способ в установке на закладке Kernel, как Вы советовали.

Сейчас весь код и данные у меня находятся в SDRAM. В будущем некоторые маленькие ISR, области данных и стек я могу загрузить в SRAM, но сейчас SRAM используется только для кэша инструкций и данных. Надеюсь, что с диапазоном перехода JUMP проблемы не будет (сейчас с этим все в порядке).

Ниже приведен практический пример встраивания вызова планировщика в прерывание TIMER0, с приоритетом IVG8.

1. Создайте обработчик прерывания на ассемблере на закладке Kernel свойств проекта, и запретите его автозапуск. Для этого в Kernel -> Interrupts -> New Interrupt -> выберите EVT_IVG8, имя исходного файла оставьте по умолчанию EVT_IVG8.asm, разрешите автоматическое создание файла модуля и добавление его в проект, язык оставьте Assembly.

VDK Kernel create New Interrupt

К сожалению, для нашего случая нельзя выбрать вариант языка C, потому что в конце обработчика прерывания нам необходимо сделать не возврат, а прямой вызов планировщика с условием сохранности всех регистров. Возврат из ISR будет выполнен в коде планировщика.

Убедитесь, что опция Enabled at Boot установлена в false, и запомните имя Entry Point, оно понадобится далее при настройке обработчика прерывания. В нашем случае имя метки точки входа в прерывание будет EVT_IVG8_Entry, его впоследствии можно будет при желании поменять.

2. Поменяйте настройки системы, скорректировав период тика в опции Kernel -> System -> Tick Period (ms) таким образом, чтобы значение периода в миллисекундах соответствовало реальному интервалу запуска планировщика. В выпадающем списке опции Timer Interrupt выберите None.

3. В начале потока, который автоматически создается и запускается при старте приложения VDK (до его входа в бесконечный цикл) создайте код настройки и запуска таймера. Пример настройки TIMER0 в режиме PWM:

extern "asm" void EVT_IVG8_Entry ();
 
..
// Настройка периода срабатывания таймера:
*pTIMER0_PERIOD = 0x00000680;
*pTIMER0_WIDTH  = 0x00000340;
// Настройка тактирования, режима работы, разрешение прерывания
// по окончанию периода счета:
*pTIMER0_CONFIG = IRQ_ENA | PERIOD_CNT | PWM_OUT;
 
// Сброс тетрады периферийного устройства TIMER0, куда записывается значение IVG:
*pSIC_IAR2 &= PX_IVG_CLR(ADI_INT_TIMER0);
// Запись нового значения IVG в тетраду периферийного устройства:
*pSIC_IAR2 |= PX_IVG(ADI_INT_TIMER0, ik_ivg8);
// Регистрация вектора обработчика прерывания для группы приоритетов IVG8:
register_handler(ik_ivg8, EVT_IVG8_Entry);
 
// Разрешение счета TIMER0:
*pTIMER_ENABLE  = 0x0001;
..

4. Добавьте в модуль EVT_IVG8.asm код для сброса флага прерывания TIMER0. Замените последнюю инструкцию RTI на передачу управления в планировщик по метке _tmk_TimerISR. Пример кода модуля приведен ниже во врезке.

#include "VDK.h"
.file_attr prefersMemNum="30";
.file_attr prefersMem="internal";
.file_attr ISR;
 
.section/doubleany data1;
/* Декларируйте здесь внешние глобальные и локальные имена */
 
.section/doubleany program;
 
/* Точка входа в ISR */
.GLOBAL     EVT_IVG8_Entry;
EVT_IVG8_Entry:
#if defined (__SILICON_REVISION__)\
         && (__SILICON_REVISION__ < 0x4 || __SILICON_REVISION__ == 0xffff)
   // Обход ошибки кристалла (anomaly) 05000283.
   // Запись системного MMR приостанавливается на бесконечное время, когда
   // прибивается на определенном этапе.
   // Обход ошибки заключается в сбросе логики MMR другим прибитым доступом к MMR,
   // у которого нет других побочных эффектов в приложении.
   // Никакие точки останова не должны быть помещены перед  меткой wa_05000283_skip.
   // Эта аномалия влияет на кристаллы до версии 0.4.
   [--SP] = P0;
   [--SP] = R0;
   [--SP] = ASTAT;
   CC = R0 == R0;    // всегда true
   P0.H = 0xffc0;    // MMR space CHIPID
   P0.L = 0x0014;
   IF CC JUMP .wa_05000283_skip;  // всегда пропускается доступ к MMR, но
                                  // доступ к MMR захватывается и прибивается.
   R0 = [P0];     // пустое чтение MMR, чтобы обойти аномалию
.wa_05000283_skip:
   ASTAT = [SP++];
   R0 = [SP++];
   P0 = [SP++];
#endif
 
#if defined (__SILICON_REVISION__)\
         && (__SILICON_REVISION__ < 0x3 || __SILICON_REVISION__ == 0xffff)
   // Обход аномалии 05000257 ADSP-BF538.
   // Перезагрузка регистров счетчика циклов, чтобы принудительно очистить
   // буферы цикла, и таким способом предотвратить отправку неправильного адреса
   // в блок выборки инструкции, если происходит короткий выход из аппаратного цикла.
   // Эта аномалия влияет на версии кремния до 0.3.
   [--SP] = LC0;
   [--SP] = LC1;
   LC1 = [SP++];
   LC0 = [SP++];
#endif
 
/* Сюда вставьте код на ассемблере, где запрещена вложенность прерываний */
   [--SP] = RETI;    // Проталкивание в стек регистра RETI снова разрешит прерывания
 
/* Сюда вставьте код на ассемблере, где разрешена вложенность прерываний */
   RETI = [SP++];    // Восстановление сохраненного RETI перед RTI. В нашем случае
                     // инструкция RTI заменяется на jump _tmk_TimerISR.
 
   // Сохранение регистров в стеке, которые используются для очистки флага таймера:
   [--SP] = P5;
   [--SP] = R4;
   
   // Загрузка адреса регистра состояния таймеров:
   P5.l = lo(TIMER_STATUS);
   P5.h = hi(TIMER_STATUS);
   R4 = 0x01;
   // Запись единички в бит 0, чтобы очистить флаг прерывания Timer0:
   [P5] = R4;
 
   // Восстановление регистров из стека:
   R4 = [SP++];
   P5 = [SP++];
 
   //RTI;
.extern _tmk_TimerISR;
   jump _tmk_TimerISR;  // Вызов VDK timer ISR.
   
.EVT_IVG8_Entry.end:

5. Добавление своего кода в ISR.

Кроме вызова планировщика, может потребоваться добавить в ISR свой код. Например, для DDS-синтеза сигнала с помощью ШИМ. Для этого целесообразно написать код обработчика прерывания на языке C, скомпилировать его в ассемблер с помощью опции -S компилятора, и добавить полученный модуль ассемблера в проект вместо модуля на языке C. И конечно же, надо не забыть поменять инструкцию RTI в конце этого модуля ISR на ассемблере на jump _tmk_TimerISR.

В этом примере модуль обработчика прерывания timer_pwm_isr.c компилируется в модуль на языке ассемблера timer_pwm_isr.asm. Опции можно подсмотреть в лог-файле компиляции VisualDSP. Ниже показан пример запуска компилятора для генерации кода ассемблера. Опции командной строки в этом примере я указал для наглядности каждую в отдельной строке, но на самом деле при вызове компилятора ccblkfn.exe все опции надо указывать в одной строке через пробел.

"C:\Program Files (x86)\Analog Devices\VisualDSP 5.1.2\ccblkfn.exe"
   -c .\timer_pwm_isr.c
   -O
   -Ov100
   -structs-do-not-overlap
   -no-multiline
   -D ADI_SSL_DEBUG_CMT -D ADI_DEV_DEBUG_CMT
   -I ..\lib -I ..\inc -I ..\lib\uart -I ..\lib\zlib -I ..\lib\ADI
   -double-size-32
   -decls-strong
   -warn-protos
   -threads
   -si-revision 0.5
   -proc ADSP-BF538
   -o .\timer_pwm_isr.asm
   -MM
   -S

Ниже в таблице показано назначение используемых опций. Подробное описание опций компилятора Blackfin см. в статье [2].

Опция Для чего нужно
-c Подавляет фазу редактирования линковки при компиляции, и не удаляет любые объектные файлы, созданные при компиляции.
.\timer_pwm_isr.c Имя компилируемого файла.
-O Включает оптимизации компилятора.
-Ov100 Оптимизация, полностью ориентированная на максимальное быстродействие кода.
-structs-do-not-overlap Подразумевается, что нет перекрытия областей памяти во время копирования структур, когда копирование может быть реализовано через стандартную функцию memcpy.
-no-multiline Запрещает поддержку строк GNU с несколькими линиями.
-D Добавляет символы для препроцессора.
-I Добавляет пути поиска подключаемых заголовков.
-double-size-32 Принудительно задает тип double в 32-битном формате IEEE одинарной точности.
-decls-strong Не инициализированные глобальные переменные рассматриваются как строго инициализированные нулем, например глобальная переменная "int x;" будет эквивалентна глобальной переменной "int x = 0;".
-warn-protos Приводит компилятор к выдаче предупреждений по поводу любых функций, для которых не предоставлены полные прототипы.
-threads Задает, что код пользователя будет линковаться с потокобезопасными библиотеками. Эта опция тем не менее не делает каких-либо проверок безопасности потоков в самом пользовательском коде. Если используется эта опция, то компилятор определит макрос _ADI_THREADS как единица. Эта опция применяется для компиляции VDK-проектов.
-si-revision Устанавливает ревизию кремния (silicon revision) для сборки, где x.x это номер ревизии для аппаратуры кристалла. Если опция -si-revision не используется, инструментарий выберет ревизию по умолчанию среди всех поддерживаемых ревизий.
-proc Инструктирует компилятор генерировать код, подходящий для указанного типа процессора.
-o Указывает имя выходного файла.
-MM Выполняет обработку препроцессора без остановки компиляции после завершения обработки.
-S Подавляет при компиляции фазы ассемблирования и редактирования линковки, в результате чего появляется файл на языке ассемблера.

6. После того, как вы заменили метку входа в обработчик прерывания (EVT_IVG8_Entry) на свою метку в своем обработчике прерывания, можно удалить в свойствах проекта на закладке Kernel обработчик прерывания, который был создан на шаге 1.

К сожалению, такой способ подмены обработчика прерывания при необходимости смены функционала в коде требует повторной перекомпиляции модуля *.c в ассемблер и ручной правки для замены инструкции RTI на вызов планировщика _tmk_TimerISR.

[Ссылки]

1. Problem changing VDK default timer interrupt site:analog.com.
2. Опции командной строки компилятора Blackfin.

 

Добавить комментарий


Защитный код
Обновить

Top of Page