Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Выход из прерывания в требуемую точку
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
Krom
Контроллер LPC1768. Задача: в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание. Никаких ОС не используется.
В контроллерах семейства 8051 это делалось элементарно:
; сохраняем регистр прерываний
push IE
; запрещаем прерывания
clr EA
; берем требуемый адрес
mov DPTR,#PROCESS
; подсовываем в стек
push DPH
push DPL
; и выходим куда нужно
reti

А вот как сделать нечто подобное в Cortex M3? Как корректно передать управление нужному процессу из обработчика прерывания, да так, чтобы ничего не испортить,а затем уже из того процесса корректно вернуться в нужное место?
scifi
Цитата(Krom @ May 17 2011, 17:48) *
Контроллер LPC1768. Задача: в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание. Никаких ОС не используется.

Прошу прощения за нескромный вопрос: зачем? Просто есть сильное подозрение, что Ваша задача на самом деле решается без привлечения таких извращений.

Цитата(Krom @ May 17 2011, 17:48) *
В контроллерах семейства 8051 это делалось элементарно:
; сохраняем регистр прерываний
push IE
; запрещаем прерывания
clr EA
; берем требуемый адрес
mov DPTR,#PROCESS
; подсовываем в стек
push DPH
push DPL
; и выходим куда нужно
reti

А вот как сделать нечто подобное в Cortex M3?

Точно так же. Подменяем адрес возврата. А ещё есть setjmp/longjmp.

Перечитав условие задачи снова
Цитата(Krom @ May 17 2011, 17:48) *
Задача: в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание.

появилась мысль: если "требуемая точка" - это функция, то решение задачи сводится к вызову функции из прерывания и возврату из прерывания сразу после вызова.
Но, опять же, есть сильное подозрение, что задача решается более "традиционными" средствами.

Дополнение:
Ещё вариант - использовать ещё одно вспомогательное прерывание с более низким приоритетом, чем UART. Устанавливаем флаг вызова этого прерывания из обработчика UART и выходим из обработчика.
kovigor
Цитата(Krom @ May 17 2011, 16:48) *
Как корректно передать управление нужному процессу из обработчика прерывания, да так, чтобы ничего не испортить,а затем уже из того процесса корректно вернуться в нужное место?


В правильно спроектированном ПО необходимости в таких действиях обычно не возникает. Продумайте структуру вашего проекта еще раз ...
VladislavS
Кусок кода, который используется и в прерывании, и в основном коде оформляете в виде функции. Озадачиваетесь вопросом что будет, если эта функция будет вызвана из прерывания во время её выполнения в основном коде. Ну и собственно всё.
klen
редизайн системы однозначно, и чем раньше тем меньше потом костылей будет. это конечно мое мнение но позволю напомнить что костыли плодятся по квадрату от времени. лучше не давать им начинать это делать.
paskal
Цитата(kovigor @ May 17 2011, 19:18) *
В правильно спроектированном ПО необходимости в таких действиях обычно не возникает. Продумайте структуру вашего проекта еще раз ...

Зря вы так. Есть масса задач где прыжок по определенному адресу при обработке прерывания наиболее оптимальный вариант.
aaarrr
Цитата(paskal @ May 17 2011, 20:04) *
Зря вы так. Есть масса задач где прыжок по определенному адресу при обработке прерывания наиболее оптимальный вариант.

Ну, хоть бы пример привели, а то так не интересно.
brag
Сохранить адрес возврата (в регистре или в памяти,зависит от задачи), подменить адрес возврата в стеке, выйти. в вашей "точке" взять сохраненный ранее адрес возврата и BX на него.
Костыль - не костыль, а иногда это довольно оптимально - все зависит от задачи...

Но на кортексе есть PendSV и Systick. в моей оси Systick используется для обработки реквестов от прерываний - прерывание заносит в очередь реквест(всего 5 ldr/str инструкций и лок прерываний на 3 из них), и устанавливает PENDST. Systick выгребает очередь и выполняет в свободное время. без всяких локов.
AHTOXA
Цитата(Krom @ May 17 2011, 19:48) *
Контроллер LPC1768. Задача: в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание. Никаких ОС не используется.
В контроллерах семейства 8051 это делалось элементарно:


В контроллерах семейства 8051 это делалось для того, чтобы изобразить "вложенные прерывания". В кортексах вложенные прерывания есть изначально, и так извращаться не требуется. То есть, ваш вариант "в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание" практически полностью эквивалентен варианту "в конце прерывания вызвать нужную функцию".

paskal
Цитата(aaarrr @ May 17 2011, 20:19) *
Ну, хоть бы пример привели, а то так не интересно.

Могу и пример, но только с тем же 51-м.
Вот тот же UART.
У нас есть сложный протокол по RS-485 где сидит много устройств. Протокол примерно такой.
1-й байт - номер устройства. Если номер наш, то обрабатываем дальше, иначе игнорируем всю посылку.
2-й байт номер протокола тут идут варианты. В зависимости от протокола мы либо принимаем, либо передаем, и количество байт разное. Но реакция должна быть мгновенной, т.е. обрабатывать мы должны на лету, это задано в ТЗ.

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

Примерно так
MOV A,SBUF
M1: CJNE A,#1, M2
;обработка протокола 1
JMP BEGIN
M2: CJNE A,#2, M3
;обработка протокола 2
JMP BEGIN
.... и.т.д.


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

Другой более простой случай. Надо дождаться на пине например 1, и тут же среагировать на это. Если в лоб ждать то будет так:

WAIT: JNB P1.0,WAIT
; обработка события

Но тогда цикл может висеть бесконечно долго, нужен выход по таймауту. Обычный метод - ввести в цикл счетчик, его инкрементировать, сравнивать и добавить дополнительный переход. Но цикл многократно удлинится, а с ним и время реакции.
Выход - оставить цикл без счетчика, а ввести таймерное прерывание с возвратом на TIMEOUT:

WAIT: JNB P1.0,WAIT
; обработка события
TIMEOUT:
; идем дальше

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

А еще таким методом можно организовать многозадачность повесив на таймер такой обработчик, чтоб он запоминал адрес предыдущего вызова, а возвращал на адрес следующей, и так перебирал все задачи по кругу.


Цитата(AHTOXA @ May 17 2011, 21:11) *
В контроллерах семейства 8051 это делалось для того, чтобы изобразить "вложенные прерывания". В кортексах вложенные прерывания есть изначально, и так извращаться не требуется. То есть, ваш вариант "в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание" практически полностью эквивалентен варианту "в конце прерывания вызвать нужную функцию".

Вовсе нет. Во-первых в 51-х можно сделать вложенное прерывание. Во-вторых как я описал выше, вложенность тут ни при чем
ReAl
Цитата(paskal @ May 17 2011, 21:15) *
А вот по прерываниям простота заканчивается. По любому приему происходит переход на один и тот же адрес обработчика и дальше надо заводить кучу дополнительных флагов чтоб помнить текущую фазу протокола, и оборачивается это кучей запутанных ветвлений.
Вот здесь положение спасает переход по определенному адресу. После обработки очередного байта, мы заносим в переменную адрес следующего обработчика и при следующем прерывании прыгаем сразу в эту точку.


Код
// Компиляторо-зависимые атрибуты/прагмы опускаем
void (*real_handler)() = initial_handler; //
void interrupt_handler()
{
    real_handler(); // внутри всех обработчиков при необходимости перезаносится переменная real_handler
}
Что-то мне кажется, что от возникновения прерывания до начала работы реального обработчика это будет быстрее, чем занесение в стек дополнительного адреса возврата для «возврата» в некий процесс.
Быстрее этого будет только перемещение таблицы векторов в ОЗУ и замена вектора в той таблице.
KnightIgor
Цитата(paskal @ May 17 2011, 20:15) *
А вот по прерываниям простота заканчивается. По любому приему происходит переход на один и тот же адрес обработчика и дальше надо заводить кучу дополнительных флагов чтоб помнить текущую фазу протокола, и оборачивается это кучей запутанных ветвлений.
Вот здесь положение спасает переход по определенному адресу. После обработки очередного байта, мы заносим в переменную адрес следующего обработчика и при следующем прерывании прыгаем сразу в эту точку.
При таком методе программа получается намного проще, а реакция на обработку быстрее.


Что Вы тут описали, это одна из возможных реализаций state machine - давно известная и наглядная штука для ветвлящихся алгоритмов.
Александр_С
А кто вам мешает использовать указатель на функцию в прерывание.
ReAl
Цитата(paskal @ May 17 2011, 21:15) *
Другой более простой случай. Надо дождаться на пине например 1, и тут же среагировать на это. Если в лоб ждать то будет так:
WAIT: JNB P1.0,WAIT
; обработка события
Уменя в таких местах было
Код
WAIT:
    JBC TOUT_BIT, process_timeout
    JNB P1.0,WAIT
а в прерывании таймаута SETB TOUT_BIT
Оно было одинаковое для всех мест ожидания с таймаутом, в том числе более сложных, чем просто ожидание уровня на ножке.
Да, это удлиннение реакции, но не такое больше, как со счётчиком.

Не думайте, что я не подменял адреса возврата, не очищал уровень прерываний для повторного входа и т.п.
Но в подавляющем большинстве случаев это просто не нужно. Хотя понятно это становится только когда наигрался в хакера.
scifi
В дополнение к вышесказанному: "Преждевременная оптимизация - корень всех зол" (С) Дональд Кнут.
VslavX
Цитата(aaarrr @ May 17 2011, 19:19) *
Ну, хоть бы пример привели, а то так не интересно.

У меня есть такой случай, при котором надо сбросить контекст прерывания и выйти в конкретную точку.
АЦП работает обычно в режиме free-running, один из каналов следит за внешним питающим напряжением (часто там аккумулятор). Непрерывный анализ этого напряжения производится в прерывании. Можно было бы отдельную высокоприоритетную задачу завести, но жаль времени на постоянное переключение контекста и место под стек, к тому же там есть ряд нюансов с критическими секциями. И вот когда это напряжение становится ниже порога и нет критической секции, то надо выключить всех потребителей и перевести изделие в режим микропотребления. При этом RTOS уже не нужна - работает специальная процедура - вот тут то и потребовалось сделать возврат из прерывания в особую точку. Еще вариант - критическая ошибка в прерывании, когда надо выдать аналог BSOD - тут тоже желательно выйти (для некоторых архитектур) из режима прерывания. Ну а вообще, да - осознанный возврат "налево" при штатной работе (особенно если еще есть RTOS) то как бы "не камильфо". Нужно только в критических случаях, RTOS после этого уже не живет, а изделие в целом выходит в останов/спячку.

paskal
Цитата(ReAl @ May 17 2011, 22:51) *
Что-то мне кажется, что от возникновения прерывания до начала работы реального обработчика это будет быстрее, чем занесение в стек дополнительного адреса возврата для «возврата» в некий процесс.

Напомню что пример был по обработке сложного протокола с большим количеством вариантов. Переход по адресу хоть и требует дополнительных действий, но происходит СРАЗУ в нужное место. А альтернатива этому методу - долго лазить по дереву state_machine с помощью флагов и ветвлений.

Цитата(KnightIgor @ May 17 2011, 22:55) *
Что Вы тут описали, это одна из возможных реализаций state machine - давно известная и наглядная штука для ветвлящихся алгоритмов.

Конечно известная. Речь не о стэйтмашине вообще, а о том что реализовать ее можно либо адресами переходов, либо набором флагов и переменных состояния с последующими ветвлениями по этим флагам. И первый способ и нагляднее и быстрее.
AHTOXA
Цитата(paskal @ May 18 2011, 01:14) *
Конечно известная. Речь не о стэйтмашине вообще, а о том что реализовать ее можно либо адресами переходов, либо набором флагов и переменных состояния с последующими ветвлениями по этим флагам. И первый способ и нагляднее и быстрее.

Дело в том, что вызов функции - это тоже "переход по адресу":) И вместо "адреса перехода" вы можете хранить в переменной "адрес функции", соответствующей текущему состоянию. О чём вам и написал ReAl.
Разница же между "вызовом функции"(он же "переход по адресу") и "подменой адреса возврата из прерывания", о которой спрашивал топикстартер, заключается в том, что во втором случае перед переходом по адресу происходит окончание прерывания. Вопрос в том, нужно ли это топикстартеру, и что ему это даст. В 8051 это давало разрешение прерываний (о чём написал я). А в кортексах - не даёт ничего. (Вернее, кое что конечно меняется, но не так радикально).
ReAl
Цитата(Krom @ May 17 2011, 16:48) *
; сохраняем регистр прерываний
push IE
; запрещаем прерывания
clr EA
; берем требуемый адрес
mov DPTR,#PROCESS
; подсовываем в стек
push DPH
push DPL
; и выходим куда нужно
reti
Кстати, если уж на то пошло, именно в таком варианте (с одной конкретной «подпрограммой завершения», в стек заносится фиксированный адрес) на 51-ых это решалось короче:
Код
tf0_isr:
    _push <PSW, ACC, AR0>
    ...
    breq @@PROCESS; ну к примеру так решили, что надо проджолжать на низком приоритете
    _pop <AR0, ACC, PSW>
@@reti:  ; в очередной раз спасибо VslavX за утилтику «локальных меток»
    reti

@@PROCESS:
    acall @@reti
   ; а вот тут и начинается код того PROCESS -- тут уже уровень прерывания очищен
   ; выполнение продолжается на уровне приоритета основной программы
    ...
    ...
    _pop <AR0, ACC, PSW>
    ret
Т.е. вместо занесения в DPTR константы и заталкивания её в стек (или через аккумулятор -- на байт длиннее, но не нужно DPTR трогать) -- одна команда acall.
reti есть что там, что там. Помнить про пушнутое и попать его -- тоже.

Цитата(VslavX @ May 17 2011, 22:10) *
При этом RTOS уже не нужна - работает специальная процедура - вот тут то и потребовалось сделать возврат из прерывания в особую точку.
Знакомо, хотя и с «суперциклом», а не с нормальной ОС.
Один из тех исключительных случаев, когда это действительно нужно.
Krom
Цитата(aaarrr @ May 17 2011, 20:19) *
Ну, хоть бы пример привели, а то так не интересно.

Пример: прерывание UART принимает пакет определенного формата (скажем, команду устройству, которая должна выполняться в фоновом режиме), которая должна быть обработана. Команда может выполняться долго, и обрабатываеть ее прямо в прерывании скажем так - не комильфо.
scifi
Цитата(Krom @ May 18 2011, 09:15) *
Пример: прерывание UART принимает пакет определенного формата (скажем, команду устройству, которая должна выполняться в фоновом режиме), которая должна быть обработана. Команда может выполняться долго, и обрабатываеть ее прямо в прерывании скажем так - не комильфо.

Некрасиво обрабатывать команды в прерывании. Обычно в прерывании реализуют приёмный буфер, а разбор и обработку команды - в главном цикле (это если без ОС) или в отдельном потоке (с ОС). А фокусы с модификацией адреса возврата и прочее - это излишняя экзотика.
Krom
Цитата(AHTOXA @ May 17 2011, 21:11) *
практически полностью эквивалентен варианту "в конце прерывания вызвать нужную функцию".

В моем варианте это не подходит. Команды могут выполняться подолгу, до нескольких секунд, а прерывание UART должно жить и отвечать мастеру, что в случае выполнения обработки этой команды в прерывании невозможно.
scifi
Цитата(Krom @ May 18 2011, 09:26) *
Команды могут выполняться подолгу, до нескольких секунд, а прерывание UART должно жить и отвечать мастеру, что в случае выполнения обработки этой команды в прерывании невозможно.

В таком случае RTOS очень помогла бы. Ну а без RTOS напрашивается использование прерываний для обработки команд, причём приоритеты прерываний соответствуют приоритетам команд.
_Pasha
Цитата(Krom @ May 18 2011, 08:15) *
Пример: прерывание UART принимает пакет определенного формата (скажем, команду устройству, которая должна выполняться в фоновом режиме), которая должна быть обработана. Команда может выполняться долго, и обрабатываеть ее прямо в прерывании скажем так - не комильфо.

Это Вы основной цикл слишком медленным сделали. Даже без РТОС можно создать приоритеты для вызываемых подзадач, и ситуёвина улучшится в разы. Приоритеты как раз могут изменяться внутри прерывания.
Krom
Цитата(scifi @ May 18 2011, 09:22) *
Некрасиво обрабатывать команды в прерывании. Обычно в прерывании реализуют приёмный буфер, а разбор и обработку команды - в главном цикле (это если без ОС) или в отдельном потоке (с ОС). А фокусы с модификацией адреса возврата и прочее - это излишняя экзотика.

ОСи нет, а главный цикл очень сложный, с вводами с клавиатуры и т.д., и когда дойдет очередь до обработки команды - хз. В 51х все работало на ура, и здесь будет wink.gif, просто нигде не могу найти детального описания - что происходит при передаче управления обработчику прерывания и при выходе из него. Например, в поисании указано, что при передаче управления обработчику прерывания в стек автоматически помещается 8 регистров: R0-R3,LR,PC,xPSR. Смотрю в листингах:
При входе в обработчик SysTick:
64:M1504/lpc17xx_it.c **** void SysTick_Handler(void)
65:M1504/lpc17xx_it.c **** {
200 .loc 1 65 0
201 .cfi_startproc
202 @ args = 0, pretend = 0, frame = 0
203 @ frame_needed = 1, uses_anonymous_args = 0
204 @ link register save eliminated.
205 0000 80B4 push {r7}
206 .LCFI14:
207 .cfi_def_cfa_offset 4
208 .cfi_offset 7, -4
209 0002 00AF add r7, sp, #0

При выходе:
248 .loc 1 74 0
249 0068 BD46 mov sp, r7
250 006a 80BC pop {r7}
251 006c 7047 bx lr
252 .cfi_endproc


Нигде нет обработки этих самых 8 регистров в стеке. Они отрабатываюся автомачически самим контроллером или? Мне бы мануал детальный найти...



scifi
Цитата(Krom @ May 18 2011, 09:47) *
а главный цикл очень сложный, с вводами с клавиатуры и т.д., и когда дойдет очередь до обработки команды - хз

Вот это и есть корень проблемы. И костыли с подменой адреса возврата из прерывания не сильно помогут, IMHO.

Цитата(Krom @ May 18 2011, 09:47) *
В 51х все работало на ура, и здесь будет wink.gif

А что же сподвигло уйти от 51-го?
Cortex тем и хорош, что с ним не нужны все эти уловки, чтобы выжать последние соки из хилого процессора. Для Cortex можно программировать на нормальном Си, ведь это нормальный 32-разрядный процессор с приличной производительностью. Переносить на него грязные хаки из 51-го выглядит неприличным.

Цитата(Krom @ May 18 2011, 09:47) *
Мне бы мануал детальный найти...

Гугл пробовали?
cortex-m3 manual
Krom
Цитата(scifi @ May 18 2011, 10:17) *
И костыли с подменой адреса

Ну так и сразу костыли... Программерский трюк это не костыль wink.gif. В любом боле-менее сносном программно-аппаратном комплексе защиты ПО таких "костылей" тьма-тьмущая, и без них вообще никак, так что-же?
Цитата(scifi @ May 18 2011, 10:17) *
А что же сподвигло уйти от 51-го?

Ну я не это предлагал обсудить...
Цитата(scifi @ May 18 2011, 10:17) *
Переносить на него грязные хаки из 51-го выглядит неприличным.

Грязные? Хаки? Чем они грязные и почему хаки?
Цитата(scifi @ May 18 2011, 10:17) *
Гугл пробовали?

Пробовал. Хотелось бы найти хорошую диаграммку с детальным описанием, как в мануале Филипса для 51х контроллеров. А то что в гугле по Кортексам разобраться конечно можно, но проштудировав много-много разных мануалов. Просто вопрос времени. Ведь мог кто-то уже сталкиваться с подобной задачей и решить ее? Мог. Почему бы мне не спросить? Вроде для того и форум.
Александр_С
Переключаться на другую задачю всегда надо через PendSV_Handler, так как прерывания могут быть вложеные.
Krom
Цитата(Александр_С @ May 18 2011, 11:01) *
Переключаться на другую задачю всегда надо через PendSV_Handler, так как прерывания могут быть вложеные.

Я за советом и обратился sm.gif Задача собственно в том и стоит, чтобы по определенному событию (получен пакет с командой) как-то переключится на обработчик этой команды (может, программное прерывание?), а уж потом из обработчика вернуться туда, откуда попали в прерывание. Вероятно, я не совсем точно сформулировал проблему, сорри...
Александр_С
//Формирование прерывания "PendSV_Handler"
*(volatile unsigned *)0xE000ED04 = 0x10000000;
VladislavS
Можно перед выходом из прерывания настроить таймер, чтобы он через некоторое количество тактов сработал и использовать его прерывание для реакции на команду.
brag
Цитата
То есть, ваш вариант "в определенный момент выйти из прерывания (UART) в требуемую точку, а уж оттуда потом продолжить выполнения с места, где возникло прерывание" практически полностью эквивалентен варианту "в конце прерывания вызвать нужную функцию".

да нет, выпав с прерывания в определенную точку то же самое прерывание может выполнится еще раз, вернувшись опять в ту точку и так рекурсивно sm.gif
Скорее рекурсивный глюк выйдет sm.gif
Хотя для кортексов это все не нужно, там есть svc,pendsv,systick, все с настраиваемыми приоритетами, все с tail-chaining, те все быстро и красиво,
_Pasha
Цитата(brag @ May 18 2011, 15:24) *
все быстро и красиво,

Да, с вложенными прерываниями - гораздо краше. Код тяжелого обработчика в SVC
d__
Можно попробовать механизм setjmp & longjmp. При этом не забыть в контроллере прерываний деинициализировать текущее прерывание.
zovsilab
Правильно ли сделал запись по формированию и вызову программного прерывания? Keil (LPC2468)

Код
.
.
#define SoftwareInterrupt asm (" swi #1")
.
.
void SWI_ISR(void) __attribute__ ((interrupt("SWI")));
main
{
    SoftwareInterrupt;    
while(1);
}
.
.
.

void SWI_ISR(void)
{
   бла-бла-бла
}
brag
ну типа правильно, не помню только что там с приоритетами, armv4 не юзал давно
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.