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 проблемы не будет (сейчас с этим все в порядке).