Наименование параметра Значение
Тема статьи: Прерывания.
Рубрика (тематическая категория) Программирование

Сторожевые таймеры.

Часто электрические помехи, производимые окружающим оборудованием, вызывают обращение микроконтроллера по неправильному адресу, после чего его поведение становится непредсказуемым (микроконтроллер ʼʼидет в раз­носʼʼ). Чтобы отслеживать такие ситуации в состав микроконтроллера часто включают сторожевые таймеры.

Это устройство вызывает сброс микроконтроллера, в случае если его содержимое не будет обновлено в течение определœенного промежутка времени (обычно от десятков миллисекунд до нескольких секунд). В случае если изменение содержимо­го программного счетчика не соответствует заданной программе, то команда модификации сторожевого таймера не будет выполнена. В этом случае сторо­жевой таймер производит сброс микроконтроллера, устанавливая его в ис­ходное состояние.

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

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

Многие пользователи считают, что прерывания - это та часть аппаратного обеспечения, которую лучше оставить в покое, так как их использование требует превосходного знания процессора для разработки программы обра­ботки прерывания. В противном случае при возникновении прерывания сис­тема ʼʼзасыпаетʼʼ или ʼʼидет вразносʼʼ. Такое чувство обычно появляется у раз­работчика после опыта работы с прерываниями для персонального компьютера, который имеет ряд особенностей, усложняющих создание об­работчика прерываний. Многие из этих проблем не имеют места в оборудова­нии, реализованном на базе микроконтроллеров. Использование в данном оборудовании прерываний может существенно упростить его разработку и применение.

В случае если вы никогда не имели дело с прерываниями, то у вас возникнет вопрос - что это такое? В компьютерной системе прерывание - это запуск специальной подпрограммы (называемой ʼʼобработчиком прерыванияʼʼ или ʼʼпрограммой обслуживания прерыванияʼʼ), который вызывается сигналом аппаратуры. На время выполнения этой подпрограммы реализация текущей программы останавливается. Термин ʼʼзапрос на прерываниеʼʼ (interrupt request) используется потому, что иногда программа отказывается подтвердить пре­рывание и выполнить обработчик прерывания немедленно (рис 2.19).

Прерывания в компьютерной системе аналогичны прерываниям в повсœед­невной жизни. Классический пример такого прерывания - телœефонный зво­нок во время просмотра телœевизионной передачи. Когда звонит телœефон, у вас есть три возможности. Первый - проигнорировать звонок. Второй - отве­тить на звонок, но сказать, что вы перезвоните позже. Третий - ответить на звонок, отложив всœе текущие дела. В компьютерной системе также имеются три подобных ответа͵ которые бывают использованы в качестве реакции на внешний аппаратный запрос.

Первый возможный ответ - ʼʼне реагировать на прерывание, пока не за­вершится выполнение текущей задачиʼʼ - реализуется путем запрещения (маскирования) обслуживания запроса прерывания. После завершения задачи возможен один из двух вариантов: сброс маски и разрешение обслуживания, что приведет к вызову обработчика прерывания, или анализ значения битов (ʼʼполлингʼʼ). указывающих на поступление запросов прерывания и непос­редственное выполнение программы обслуживания без вызова обработчика прерывания. Такой метод обработки прерываний используется, когда требу­ется обеспечить заданное время выполнения основной программы, так как любое прерывание может нарушить реализацию крайне важно го интерфейса.

Рис. 2.18 - Выполнение прерывания.

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

Обработчик прерывания всœегда обеспечивает следующую последователь­ность действий:

2. Сбросить контроллер прерываний и оборудование, вызвавшее запрос.

3. Обработать данные.

4. Восстановить содержимое регистров контекста.

5. Вернуться к прерванной программе.

Регистры контекста - это регистры, определяющие текущее состояние выполнения основной программы. Обычно к их числу относятся программ­ный счетчик, регистры состояния и аккумуляторы. Другие регистры процессора, такие как индексные регистры, бывают использованы в процессе обработки прерывания, в связи с этим их содержимое также крайне важно сохра­нить. Все остальные регистры являются специфическими для конкретного микроконтроллера и его применения.

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

Вложенные прерывания сложны для реализации некоторыми типами мик­роконтроллеров, которые не имеют стека. Эти прерывания могут также выз­вать проблемы, связанные с переполнением стека. Проблема переполнения актуальна для микроконтроллеров из-за ограниченного объёма их памяти данных и стека: последовательность вложенных прерываний может привести к тому, что в стек будет помещено больше данных, чем это допустимо.

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

Восстановление регистров контекста и выполнение команды возврата из прерывания переводит процессор в состояние, в котором он находился до возникновения прерывания.

Рассмотрим, что происходит с содержимым различных регистров при обработке прерывания. Содержимое регистра состояния обычно автомати­чески сохраняется вместе с содержимым программного счетчика перед обра­боткой прерывания. Это избавляет от крайне важно сти сохранять его в памяти программными средствами с помощью команд пересылки, а затем восста­навливать при возврате к исходной программе. При этом такое автоматическое сохранение реализуется не во всœех типах микроконтроллеров, в связи с этим орга­низации обработки прерываний следует уделить особое внимание.

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

В случае если содержимое других регистров процессора изменяется при выполне­нии обслуживания прерывания, то оно также должно быть сохранено в памяти до изменения и восстановлено перед возвратом в основную программу. Обычно принято сохранять всœе регистры процессора, чтобы избежать не­предсказуемых ошибок, которые очень трудно локализовать.

Адрес, который загружается в программный счетчик при переходе к обра­ботчику прерывания, принято называть ʼʼвектор прерыванияʼʼ. Существует несколь­ко типов векторов. Адрес, который загружается в программный счетчик при запуске микроконтроллера (reset) принято называть ʼʼвектор сбросаʼʼ. Для различных прерываний бывают заданы разные вектора, что избавляет программу обслуживания от крайне важно сти определять причину прерывания. Использо­вание различными прерываниями одного вектора обычно не вызывает про­блем при работе микроконтроллеров, так как чаще всœего микроконтроллер исполняет одну единственную программу. Этим микроконтроллер отличается от персонального компьютера, в процессе эксплуатации которого могут до­бавляться различные источники прерываний. (В случае если вы когда-либо подклю­чали два устройства к портам СОМ1 и COM3, то вы представляете, о чем идет речь). В микроконтроллере, где аппаратная часть хорошо известна, не должно возникнуть каких-либо проблем при совместном использовании век­торов прерываний.

Последнее, что осталось рассмотреть, - это программные прерывания. Существуют процессорные команды, которые бывают использованы для имитации аппаратных прерываний. Наиболее очевидное использование этих команд - это вызов системных подпрограмм, которые располагаются в про­извольном месте памяти, или требуют для обращения к ним межсегментных переходов. Эта возможность реализована в микропроцессорах семейства Intel i86 и используется в базовой системе ввода-вывода BIOS (Basic Input/Output System) и операционной системе DOS персональных компьютеров для вызо­ва системных подпрограмм без крайне важно сти фиксирования точки входа. Вместо этого используются различные вектора прерываний, выбирающие команду, которая должна выполняться, когда происходит такое программ­ное прерывание.

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

Прерывания. - понятие и виды. Классификация и особенности категории "Прерывания." 2017, 2018.

В архитектуре ПЭВМ базовая система ввода/вывода (БСВВ) занимает особое место. Ее можно рассматривать, с одной стороны, как составляющую часть аппаратных средств, с другой стороны, это система является одним из программных модулей ОС.

Одной из важных функция БСВВ – обслуживание системных вызовов, или прерываний. Системные вызовы вырабатываются программными или аппаратными средствами с целью выполнения различных операций. Для реализации системных вызовов используется механизм прерываний. Суть этого механизма заключается в том, что текущая работа машины, в чем бы она ни состояла, может быть приостановлена на короткое время одним из сигналов, который указывает на возникновение ситуации, требующей немедленной обработки

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

Событие может быть вызвано особой ситуацией, сложившейся при выполнении программы, или сигналом от внешнего устройства. Прерывание используется для быстрой реакции процессора на особые ситуации, возникающие при выполнении программы и взаимодействии с внешними устройствами.

При прерывании ОС сохраняет состояние процессора – значения регистров и значение счетчика команд (program counter – PC) – адреса прерванной команды. Обработчик прерывания в ОС определяет по содержимому сегмента объектного кода, какого вида прерывание возникло и какие действия по его обработке следует предпринять. Среди возможных видов прерываний, кроме фиксации различных ошибок, имеются также прерывания по таймеру – периодические прерывания через определенный квант времени, предназначенные для опроса устройств (polling) – действий операционной системы по периодической проверке состояния всех портов и внешних устройств, которое может меняться с течением времени: например, к USB-порту была подключена флэшка; принтер закончил печать и освободился, и т.д. ОС выполняет реконфигурацию системы и корректирует системные таблицы, хранящие информацию об устройства



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

Любая особая ситуация, вызывающая прерывание, сопровождается сигналом, называемым запросом прерывания (ЗП). Запросы прерываний от внешних устройств поступают в процессор по специальным линиям, а запросы, возникающие в процессе выполнения программы, поступают непосредственно изнутри микропроцессора. Механизмы обработки прерываний обоих типов схожи. Рассмотрим функционирование компьютера при появлении сигнала запроса прерывания, опираясь в основном на обработку аппаратных прерываний (рис. 13).

Рис. 13. Выполнение прерывания в компьютере:

tр - время реакции процессора на запрос прерывания;

tс - время сохранения состояния прерываемой программы и вызова обработчика прерывания;

tв - время восстановления прерванной программы.

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

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

Время реакции зависит от момента, когда процессор определяет факт наличия запроса прерывания. Опрос запросов прерываний может проводиться либо по окончании выполнения очередного этапа команды (например, считывание команды, считывание первого операнда и т.д.), либо после завершения каждой команды программы.

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

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

Логические, или процессорные, прерывания возникают при различных нестандартных ситуациях в работе основного микропроцессора – делении на нуль, переполнении регистров, появлении «точки останова» и др.

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

Каждое прерывание имеет уникальный номер и с ним может быть связана определенная подпрограмма, призванная обслуживать возникшую ситуацию. Сама обслуживающая подпрограмма, вообще говоря, тоже может быть приостановлена другим прерыванием; но чаще всего на период работы подпрограммы, обслуживающей некоторое прерывание, другие прерывания «маскируются», т. е. не обслуживаются немедленно, а становятся в очередь.

В аппаратных компонентах машины в самой ДОС и в прикладных программах могут вырабатываться прерывания, которые нужно обслуживать. На БСВВ возлагается задача обслуживания прерываний нижнего уровня – тех, которые требуют непосредственного управления аппаратными компонентами. Этим прерываниям присвоены номера с 0 по 31 (шестнадцатеричные номера 0 – 1F). Другие прерывания – с номерами 32 – 63 (шестнадцатеричные номера 20 – 3F) – относятся к более высокому уровню, и их обслуживание возлагается на другие модули ОС.

В табл. 3 приведен общий перечень прерываний, обслуживаемых БСВВ. В реальных программах на языке ассемблера и в технической литературе по ОС прерывания идентифицируются шестнадцатеричиыми кодами. Из анализа табл. 3 видно, что обслуживаемые БСВВ прерывания соответствуют базовым операциям по управлению внешними устройствами – дисплеем, клавиатурой, НГМД, принтером, коммуникационными каналами. При этом подпрограммы, входящие в БСВВ, выполняют операции нижнего уровня. Так, например, обслуживание НГМД включает возможность начальной установки магнитных головок, проверки текущего статуса устройства, прямого чтения и записи заданных секторов диска, верификации прочитанных или записанных данных и, наконец, форматирования (начальной разметки) дисков.

Таблица 3. Прерывания, обслуживаемые БСВВ

Деся- тичный номер Шестнадца- теричный номер
Деление на ноль
Перевод микропроцессора в пошаговый режим
Падение напряжения питания
Появление точки останова в последовательности команд
Переполнение регистров арифметического устройства
Печать графической копии экрана
Зарезервировано
Зарезервировано
Сигнал от счетчика времени – таймера
Сигнал от нажатия клавиши на клавиатуре
А Зарезервировано
Деся- тичный номер Шестнадца- теричный номер Обслуживаемая ситуация или выполняемая функция
В Зарезервировано
С Зарезервировано
D Зарезервировано
Е Сигнал об окончании обмена с НМД
F Зарезервировано для обслуживания принтера
Управление дисплеем
Запрос списка подсоединенного оборудования
Запрос размера физической памяти
Управление НМД
Управление коммуникационным адаптером
Управление магнитофоном и другими устройствами
Управление клавиатурой
Управление принтером
Обращение к встроенному в ПЗУ бейсику
Перезапуск системы
Запрос/установка текущего времени и даты
1D Адрес таблицы параметров инициализации дисплея.
1E Адрес таблицы параметров НГМД
1F Адрес таблицы символов с кодами 128-255

Некоторые из указанных в табл. 3 прерываний обеспечивают доступ к нескольким взаимосвязанным функциям. Каждая функция идентифицируется своим шестнадцатеричным номером (кодом) и обеспечивает выполнение некоторой частной операции.

Так, например, прерывание 19 (управление НГМД и НМД) открывает доступ к 18 функциям с кодами 0-17):

0 - начальная установка (сброс диска),

1 - выдача текущего статуса диска,

2 - чтение группы (блока) секторов с одной дорожки,

3 - запись группы секторов на одну дорожку,

4 - верификация после чтения или записи,

5 - форматирование дорожки (запись меток секторов),

8 - выдача текущих параметров накопителя,

9 - инициализация таблицы параметров фиксированного диска,

А - «длинное» чтение,

В - «длинная» запись,

С - поиск нужной дорожки,

D - начальная установка диска,

10 - проверка готовности диска,

11 - калибровка диска,

14 - диагностика контроллера,

15 - выдача типа накопителя,

16 - изменение статуса диска,

17 - установка типа накопителя.

Глубина прерывания - максимальное число программ, которые могут прерывать друг друга. Глубина прерывания обычно совпадает с числом уровней приоритетов, распознаваемых системой прерываний. Работа системы прерываний при различной глубине прерываний (n) представлена на рис. 10. Здесь предполагается, что с увеличением номера запроса прерывания увеличивается его приоритет.

Рис. 14. Работа системы прерываний при различной глубине прерываний

При поступлении запроса прерывания компьютер выполняет следующую последовательность действий:

1) определение наиболее приоритетного незамаскированного запроса на прерывание (если одновременно поступило несколько запросов);

2) определение типа выбранного запроса;

3)сохранение текущего состояния счетчика команд и регистра флагов;

4) определение адреса обработчика прерывания по типу прерывания и передача управления первой команде этого обработчика;

5) выполнение программы - обработчика прерывания;

6)восстановление сохраненных значений счетчика команд и регистра флагов прерванной программы;

7) продолжение выполнения прерванной программы.

Этапы 1-4 выполняются аппаратными средствами ЭВМ автоматически при появлении запроса прерывания. Этап 6 также выполняется аппаратно по команде возврата из обработчика прерывания.

Переход к соответствующему обработчику прерывания осуществляется (в реальном режиме работы микропроцессора) посредством таблицы векторов прерываний. Эта таблица располагается в самых младших адресах оперативной памяти, имеет объем 1 Кбайт и содержит значения сегментного регистра команд (CS) и указателя команд (IP) для 256 обработчиков прерываний.

Вектор прерывания – ячейка памяти, содержащая адрес обработчика прерывания.

Вектора прерываний объединяются в таблицу векторов прерываний. Местоположение таблицы зависит от типа и режима работы микропроцессора.

Главные функции механизма прерывания:

1.Распознавание или классификация прерывания.

2. Передача управления обработчику прерывания.

3. Корректное возвращение к прерванной программе

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

Поскольку сигналы прерывания возникают в произвольные моменты времени, то на момент прерывания может существовать несколько сигналов прерывания, которые могут быть обработаны только последовательно. Чтобы обработать сигналы прерывания в разумном порядке им присваиваются приоритеты. Программы, управляя специальными регистрами маски, позволяют реализовать различные дисциплины обслуживания:

Вложенные прерывания.

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

В семействе STM8 заложена очень полезная возможность экономии энергии в случае, когда быстрые и критичные ко времени обработки выполняются по прерываниям, а низкоприоритетные задачи работают в фоновом режиме. Для этого используется бит AL в регистре GCR и машинная команда WFI. Однако здесь был обнаружен подводный камень, не описанный в текущей версии errata на кристалл.

Данная проблема была обнаружена на кристалле stm8l152c6t6, установленном на STM8L-Discovery board.
В основном процессе был инициализирован таймер TIM4 для работы по прерываниям. Обработчик прерывания для него практически пустой (ну за исключением процедуры сброса бита TIM_SR1_UIF в регистре TIM4->SR1). Далее в основном процессе была разрешена запись в EEPROM путем разблокировки MASS и инициирована процедура записи байта с генерацией IRQ по ее окончании. После чего в регистре GCR был установлен бит AL и выполнена команда WFI.
В обработчике прерываний по завершению операции записи в EEPROM после чтения содержимого FLASH->IAPSR понижается приоритет выполняемого кода командой RIM или комбинацией PUSH #val/POP CC. Т.е. внутри EEPROM ISR разрешаются все остальные прерывания. И было обнаружено следующее: если EEPROM ISR была прервана другим прерыванием, то после возврата из вложенного прерывания выполнение обработки EEPROM ISR прекращается (т.е. такое впечатление, что CPU core переходит в состояние WFI, выполненное основным процессом).
Данная ошибка проявляется только при наличии следующих условий:

  1. Перед выполнением WFI в основном процессе бит AL в регистре GCR был установлен
  2. Приоритет EEPROM IRQ оставлен по умолчанию (т.е. содержимое регистра ITC->ISPR1 равно 0xFF)

Возможные workarounds:

  1. В основном процессе до выполнения инструкции WFI сбросить бит AL в GCR. При этом после каждого прерывания основной процесс будет возобновлять свое выполнение, что не очень хорошо скажется на энергопотреблении.
  2. Перед понижением приоритета внутри EEPROM ISR (т.е. до команд RIM или PUSH #val/POP CC) также сбросить бит AL в GCR. Опять-таки, в этом случае после завершения EEPROM ISR основной процесс продолжит свое выполнение, что не очень хорошо скажется на энергопотреблении.
  3. Изменить приоритет EEPROM IRQ в регистре ITC->ISPR1, например записав в него значение 0b11110111. Что самое непонятное, после этого начинают нормально работать команды RIM или PUSH #val/POP CC внутри EEPROM ISR (т.е. после возврата из вложенного прерывания обработка EEPROM ISR возобновляется).

Господа, у кого есть желание/возможность, попробуйте воспроизвести этот баг на других кристаллах семейства STM8/STM8L. Ибо если этот баг действительно воспроизводим, то надо пнуть STM на предмет его исправления или хотя бы внесения в errata sheet.

Получен официальный ответ от ST MCU Support Team:

SOLUTION PROPOSED BY SUPPORTER - 5/4/2017 15:52:59:
- Dear customer,

When an interrupt configure priority level to Level 0 (main), other interrupt executing IRET with AL bit set may put device back to WFI instead of execution of following instructions of previous code, as a consequence of priority level forced to Level 0 previously.

I recommend to use rather SW priorities than using RIM in the interrupt routine.

Best regards,
ST MCU Support Team

3. Примеры прерываний

3.1. Прерывание по изменению сигнала на портах ввода/вывода (пример в PROTEUS)

3.2. Внешнее прерывание INT (пример в PROTEUS)

У всех типов микроконтроллеров архитектуры dsPIC прерывания одинаковые. Поэтому можно смело изучать прерывания в программе PROTEUS. Для исследования будем использовать микроконтроллер, который имеется в PROTEUS.

Итак, какие же существуют прерывания, в микроконтроллере dsPIC33FJ32GP204?

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

В общем, нужно знать, что для микроконтроллера существует таблица векторов прерываний, которая располагается в памяти программ. Каждое прерывание имеет свой вектор, т. е. адрес начала подпрограммы обработки конкретного прерывания (ISR). Также имеется альтернативная таблица векторов прерывания. При помощи одного бита ALTINV можно указать, из какой таблицы нужно брать адрес подпрограммы прерывания. Т. е. есть альтернатива основной таблице. Но нужно помнить, что бит ALTINV используется сразу для всей таблицы. И если установлена альтернативная таблица, то абсолютно все адреса подпрограммы обработки прерываний, основная программа будет брать из альтернативной таблицы. Альтернативная таблица очень полезна для режима отладки, так как позволяет один раз записать в контроллер программу и при помощи, к примеру, кнопки выбирать между двумя алгоритмами работы программы.

В dsPIC расширили диапазон приоритетов прерываний. Если в PIC18 их было только 2, то в dsPIC уже 8 уровней. И когда я разрабатывал модуль для связи по каналу GPRS, мне это очень пригодилось, так как в микроконтроллере использовалась практически вся имеющаяся на борту периферия, а количество обрабатываемых прерывания было около двадцати.

Самое главное нужно знать, что для работы с каждым прерыванием существует всего 3 основных бита (регистра): бит разрешение прерывания, флаг прерывания, три бита выбора приоритета прерывания.

Итак, что же нужно от программиста для работы с прерыванием:

1. Ему нужно разрешить какое-нибудь конкретное прерывание.

2. После того как наступило событие, вызвавшее прерывание, устанавливается флаг соответствующего прерывания, и программа, так сказать, переходит по вектору прерывания. А если нормальным языком сказать: то, как только произойдёт прерывание, так сразу будет вызвана подпрограмма обработки конкретного прерывания.

3. Главное не забыть в подпрограмме прерывания сбросить флаг этого самого прерывания.

Для микроконтроллера dsPIC компилятор С30 предусматривает следующее правило описание подпрограмм обработки прерывания, например для прерывания INT1:

void __ attribute __((__ interrupt __)) _ INT 1 Interrupt ()

Главное нужно обратить внимание на концовку текста заголовка подпрограммы: _ INT 1 Interrupt () – это единственный параметр который обязательно нужно изменять для различных видов прерываний. Для каждого прерывания из таблицы векторов прерывания имеется своё обозначение данного параметра. И тут уж придётся открыть файл помощи к компилятору С30, в частности раздел (у меня это…) «dsPIC30F DSCs (SMPS) Interrupt Vectors» и посмотреть таблицу какое прерывание каким образом должно вызываться. В данной статье приводить эту таблицу не считаю целесообразным, так как у того, кого есть компилятор С30, у того должен быть вложен файл помощи hlpMPLABC30.chm (лично у меня именно так называется). А тем, у кого нет данного компилятора, то и таблица им незачем J. Ладно, отвлеклись…

Кстати, если более глубже познакомится с принципами описания функции прерывания, то можно будет обнаружить наличие специального атрибута auto_psv. Этот параметр предназначен для указания микроконтроллеру, что главные регистры он должен сохранить до того, как перейти к обработке прерывания. Я предполагаю, что вы знаете для чего нужно сохранять ключевые регистры.

Например, описание прерывания будет выглядеть следующим образом:

void __attribute__((interrupt, auto_psv)) _INT1Interrupt()

Выше был приведён пример заголовка вызова подпрограммы используя адрес основной таблицы. А для альтернативной таблице нужно только в изменяемом параметре после подчёркивания вставить Alt , например:

_ INT 1 Interrupt () -> _ AltINT 1 Interrupt ()

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

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

3.1. Прерывание по изменению сигнала на портах ввода/вывода (CN )

Одно из самых простых для понимания прерывание – это прерывание по изменению состояние на входе CN. В микроконтроллере dsPIC33FJ32GP204 полно таких входов, так что, думаю, это количество удовлетворит любые запросы. Не важно, с какого на какое изменяется состояние на этих каналах («1» -> «0» или «0» -> «1»), это изменение, если оно разрешено, вызовет установление флага «CNIF». Для того, чтобы активизировать прерывание по изменению сигнала, нужно проделать следующие действия:

1. Настроить необходимые каналы CN на вход (с помощью регистра TRISх).

2. Включить контроль изменения сигнала на соответствующем входе CN. Для этого есть аж 2 регистра CNEN1 и CNEN2. Можно как целиком обращаться к каждому регистру для настройки, либо обращаться к соответствующим битам (например _CN15IE=1; _CN6IE=1;)

3. Если необходимо, то включить подтягивающие резисторы. Для этого есть также два регистра CNPU1 и CNPU2. Можно и по отдельности, к примеру _CN15PUE=1; _CN6PUE=1;

4. Разрешить прерывание по изменению сигнала на CN (_ CNIE =1 )

5. Теперь как только изменится сигнал на контролируемых выводах CN, установится флаг прерывания _CNIF. И программа заходит в функцию обработки прерывания. Компилятор С30 для прерывания по изменению сигнала на CN предусмотрел следующее описание функции:

void __ attribute __((__ interrupt __)) _ CNInterrupt ()

Именно здесь происходит обработка данного прерывания (смотреть пример)

6. В подпрограмме обработки прерывания не забываем сбросить флаг прерывания.

Для ознакомления с данным видом прерывания рассмотрим следующий пример. Имеется двигатель и 4 датчика. Когда всё в порядке, то двигатель должен вращаться против часовой стрелки. Но как только состояние датчика изменится (аварийный режим) – двигатель должен немедленно начать вращаться в обратную сторону (по часовой стрелки). А через заданный промежуток времени вновь начать вращаться против часовой стрелки.

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

Собираем схему в PROTEUS

А теперь составляем программу для выполнения поставленной задачи

# include " p 33 Fxxxx . h "

_ FOSCSEL (0 x 02);

_ FOSC (0 xE 2);

char state ; // переменная хранит направление вращения двигателя

// "1" - аварийный режим, "0" - нормальный режим

void init (void );

void init (void )

{ _ CN 8 PUE =1; //включаем подтягивающий резистор на вход CN8 (RС0)

_ CN 10 PUE =1; //включаем подтягивающий резистор на вход CN10 (RС2)

_ CN 17 PUE =1; //включаем подтягивающий резистор на вход CN17 (RС7)

_ CN 19 PUE =1; //включаем подтягивающий резистор на вход CN19 (RС9)

AD 1 PCFGL =0 xffff ;

PORTC =0; // инициализируем порт С

LATC =0;

TRISC =0 xFFFF ; // настраиваем порт C как вход

PORTC =0 xFFFF ;

PORTA =0; // инициализируем порт A

LATA =0;

TRISA =0; // Все вывод настраиваем на выход,

PORTA =0;

_ CN 8 IE =1; // включаем контроль изменения сигнала на

_ CN 10 IE =1; // соответствующих выводах

_ CN 17 IE =1; // если на них изменится сигнал, то

_ CN 19 IE =1; // установится флаг прерывания _CNIF

_ CNIE =1; // разрешаем прерывание INT1

void __ attribute __((__ interrupt __)) _ CNInterrupt ()

state =1; // это аварийный режим

_ CNIF =0; // сбрасываем флаг прерывания по изменению сигнала

void main (void )

{ static long int i ;

init ();

state =0; // считаем, что режим работы при старте нормальный

while (1) // запускаем бесконечный цикл

{ if (state ) // если режим аварийный, то

{ _ CNIE =0; // запрещаем прерывание по изменению сигнала

_ RA 8=0;

_ RA 0=1; // в аварийном режиме

for (i =0; i <210000; i ++) //обеспечиваем задержку

state =0; // активизируем нормальный режим работы двигателя

_ CNIE =1; // разрешаем прерывания CN

{ _ RA 8=1; // в нормальном режиме

_ RA 0=0; // обеспечиваем направления вращения двигателя

} // while(1)

3.2. Внешнее прерывание INT

Если вам необходимо, чтобы ваш микроконтроллер незамедлительно реагировал на действия внешних устройств, то прерывание INT – это то, что вам нужно. В микроконтроллере dsPIC33FJ32GP204 есть три канала INT (INT0, INT1, INT2). Т. е. вы можете для каждого из трёх внешних устройств предусмотреть своё прерывание. Данное прерывание хорошо тем, что оно может вывести контроллер из режима SLEEP. К тому же вам не нужно непрерывно сканировать данные входы, что отнимало бы ресурсы микроконтроллера.

Прерывание INT возникает при изменении сигнала на входе либо «0» -> «1» либо «1» -> «0». Это зависит от состояния бита _INT1EP . Если _INT1EP =1, то по заднему фронту; если _INT1EP =0, то по переднему фронту (например _INT1EP =1 – прерывание INT0 происходит по заднему фронту).

Что касается приоритета, то прерывание INT0 имеет наивысший приоритет, причём в таблице прерываний он является самым первым. Ниже располагаются прерывания INT1 и INT2.

В принципе это и все особенности данного прерывания. Всё остальное как и у других прерываний, т. е. нужно разрешить соответствующее внешнее прерывание. И если произойдёт прерывание, то оно установит соответствующий флаг _INTхIF (например _INT1IF). Не забываем его сбросить. В общем, всё проще простого.

Переходим к примеру

Эмитируем электронный звонок в квартиру или дом. Устройство должно при нажатии на кнопку выдать звуковой сигнал и подмигнуть светом (для тех кто любит громко слушать музыку).

Составляем схему в PROTEUS, нам понадобится дополнительно к микроконтроллеру ещё кнопка, пищалка и светодиод.

Думаю на рисунке всё предельно ясно. Перейдём к программе.

#include "p33Fxxxx. h"

// Устанавливаем биты настройки генератора (HS)

_ FOSCSEL (0 x 02);

_ FOSC (0 xE 2);

char state; // "1" - включить звонок, "0" - звонок отключён

void init (void); //объявляем подпрограмму инициализации

// ** подпрограмма инициализация **

void init (void)

{ _CN4PUE=1; //включаем подтягивающий резистор на вход CN4 (RB0)

AD1PCFGL=0xffff; // все выводы как цифровые I/O

PORTB=0x00; // инициализируем порт В

LATB=0;

TRISB=0x0001; // вывод RB0 настраиваем на вход

PORTB=0x00;

// инициализации порта C.

PORTC=0;

LATC=0;

TRISC=0; //устанавливаем все выводы как выход

PORTC=0;

RPINR0=0x0000; // сигнал INT1 настраиваем, чтобы снимать с вывода RP0 (RB0) (данная

// команда не для всех dsPIC

_INT1EP=1; // прерывание INT1 происходит по заднему фронту

_INT1IE=1; // разрешаем прерывание INT1

// ******* подпрограмма обработки прерывания *

void __ attribute __((__ interrupt __)) _ INT 1 Interrupt ()

state =1; // необходимо включить звонок

_ INT 1 IF =0; // сбрасываем флаг прерывания INT1

// **** Точка входа в программу *

void main (void )

{ static long int i ; //объявляем переменную, чтобы организовать задержку

init(); // вызываем подпрограмму инициализации

state=0; // вначале звонок должен быть выключен

while(1) // запускаем бесконечный цикл

{ if (state) // если state равен "1", то

{ _RC0=1; // зажигаем светодиод

_RC2=1; // включаем пищалку

for(i=0;i<160000;i++) // задержка, обеспечивает определённое время звучания

state=0; // указываем, что пищалку нужно отключить

else // иначе, если state равна "0", то

{ _RC0=0; // гасим светодиод

_RC2=0; // прекращаем звуковой сигнал

} // while(1)

Аналогично работать и с прерываниями INT0 и INT2. В дальнейших примерах мы ещё не раз будем пользоваться этими прерываниями.

Бывает такая ситуация, когда надо на один периферийный девайс повесить много разных задач, а он всего один и что то надо с этим делать.

Простой пример — таймер и его прерывание по переполнению.
Мы можем задавать выдержку и по прерыванию делать какие-нибудь операции. Но если в один момент времени мы хотим чтобы таймер по прерванию сделал одну операцию, а потом другую, третью. Да сколько угодно, в зависимости от состояния. А вектор один.

Или, например, USART. Нам запросто может потребоваться, чтобы в зависимости от режима на прерывание по приходу байта выполнялся разный код. В одном режиме — выдача приветствия, в другом посыл матом в баню. В третьем удар в голову. А вектор один.

Конечно, можно добавить в обработчик прерывания switch-case конструкцию и по выбору режима перейти на нужный участок кода, но это довольно громоздко, а самое главное — время перехода будет разное, в зависимости от того в каком порядке будет идти опрос-сравнение switch-case структуры.

То есть в свитче вида:

1 2 3 4 5 6 7 switch (x) { 1 : Действие 1 2 : Действие 2 3 : Действие 3 4 : Действие 4 }

switch(x) { 1: Действие 1 2: Действие 2 3: Действие 3 4: Действие 4 }

Будет последовательное сравнение х вначале с 1, потом с 2, потом с 3 и так до перебора всех вариантов. А в таком случае реакция на Действие 1 будет быстрей чем реакция на Действие 4. Особо важно это при расчете точных временных интервалов на таймере.

Но есть простое решение этой проблемы — индексный переход. Достаточно перед тем как мы начнем ожидать прерывание предварительно загрузить в переменные (а можно и сразу в индексный регистр Z) направление куда нам надо перенаправить наш вектор и воткнуть в обработчик прерывания индексный переход. И вуаля! Переход будет туда куда нужно, без всякого сравнения вариантов.

В памяти создаем переменные под плавающий вектор:

Timer0_Vect_L: .byte 1 ; Два байта адреса, старший и младший Timer0_Vect_H: .byte 1

Подготовка к ожиданию прерывания проста, мы берем и загружаем в нашу переменную нужным адресом

CLI ; Критическая часть. Прерывания OFF LDI R16,low(Timer_01) ; Берем адрес и сохраняем STS Timer0_Vect_L,R16 ; его в ячейку памяти. LDI R16,High(Timer_01) ; Аналогично, но уже со старшим вектором STS Timer0_Vect_H,R16 SEI ; Прерывания ON

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

А обработчик получается вида:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 ;============================= ; Вход в прерывание по переполнению от Timer0 ;============================= TIMER_0: PUSH ZL ; сохраняем индексный регистр в стек PUSH ZH ; т.к. мы его используем PUSH R2 ; сохраняем R2, т.к. мы его тоже портим IN R2,SREG ; Извлекем и сохраняем флаговый регистр PUSH R2 ; Если не сделать это, то 100% получим глюки LDS ZL,Timer0_Vect_L ; загружаем адрес нового вектора LDS ZH,Timer0_Vect_H ; оба байта. CLR R2 ; Очищаем R2 OR R2,ZL ; Проверяем вектор на ноль. Иначе схватим аналог OR R2,ZH ; reset"a. Проверка идет через операцию OR BREQ Exit_Tm0 ; с накоплением результата в R2 ; так мы не портим содержимое Z и нам не придется; загружать его снова IJMP ; Уходим по новому вектору; Выход из прерывания. Exit_Tm0: POP R2 ; Достаем и восстанавливаем регистр флагов OUT SREG,R2 POP R2 ; восстанавливаем R2 POP ZH ; Восстанавливаем Z POP ZL RETI ; Дополнительный вектор 1 Timer_01: NOP ; Это наши новые вектора NOP ; тут мы можем творить что угодно NOP ; желательно недолго - в прерывании же NOP ; как никак. Если используем какие другие NOP ; регистры, то их тоже в стеке сохраняем RJMP Exit_Tm0 ; Это переход на выход из прерывания; специально сделал через RJMP чтобы; Дополнительный вектор 2 ; сэкономить десяток байт на коде возврата:))) Timer_02: NOP NOP NOP NOP NOP RJMP Exit_Tm0 ; Дополнительный вектор 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0

;============================= ; Вход в прерывание по переполнению от Timer0 ;============================= TIMER_0: PUSH ZL ; сохраняем индексный регистр в стек PUSH ZH ; т.к. мы его используем PUSH R2 ; сохраняем R2, т.к. мы его тоже портим IN R2,SREG ; Извлекем и сохраняем флаговый регистр PUSH R2 ; Если не сделать это, то 100% получим глюки LDS ZL,Timer0_Vect_L ; загружаем адрес нового вектора LDS ZH,Timer0_Vect_H ; оба байта. CLR R2 ; Очищаем R2 OR R2,ZL ; Проверяем вектор на ноль. Иначе схватим аналог OR R2,ZH ; reset"a. Проверка идет через операцию OR BREQ Exit_Tm0 ; с накоплением результата в R2 ; так мы не портим содержимое Z и нам не придется; загружать его снова IJMP ; Уходим по новому вектору; Выход из прерывания. Exit_Tm0: POP R2 ; Достаем и восстанавливаем регистр флагов OUT SREG,R2 POP R2 ; восстанавливаем R2 POP ZH ; Восстанавливаем Z POP ZL RETI ; Дополнительный вектор 1 Timer_01: NOP ; Это наши новые вектора NOP ; тут мы можем творить что угодно NOP ; желательно недолго - в прерывании же NOP ; как никак. Если используем какие другие NOP ; регистры, то их тоже в стеке сохраняем RJMP Exit_Tm0 ; Это переход на выход из прерывания; специально сделал через RJMP чтобы; Дополнительный вектор 2 ; сэкономить десяток байт на коде возврата:))) Timer_02: NOP NOP NOP NOP NOP RJMP Exit_Tm0 ; Дополнительный вектор 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0

Реализация для RTOS
Но что делать если у нас программа построена так, что весь код вращается по цепочкам задач через диспетчер RTOS? Просчитать в уме как эти цепочки выполняются относительно друг друга очень сложно. И каждая из них может попытаться завладеть таймером (конечно не самовольно, с нашей подачи, мы же программу пишем, но отследить по времени как все будет сложно).
В современных больших осях на этот случай есть механизм Mutual exclusion — mutex. Т.е. это своего рода флаг занятости. Если какой нибудь процесс общается, например, с UART то другой процесс туда байт сунуть не смеет и покорно ждет пока первый процесс освободит UART, о чем просемафорит флажок.

В моей механизмов взаимоисключений нет, но их можно реализовать. По крайней мере сделать некоторое минимальное подобие. Полноценную реализацию всего этого барахла я делать не хочу, т.к. моей целью является удержания размера ядра на уровне 500-800 байт.
Проще всего зарезервировать в памяти еще один байт — переменную занятости. И когда один процесс захватывает ресурс, то в эту переменную он записывает время когда ориентировочно он его освободит. Время идет в тиках системного таймера которое у меня 1ms.
Если какой либо другой процесс попытается обратиться к этому же аппаратному ресурсу, то он вначале посмотрит на состояние его занятости, считает время в течении которого будет занято и уйдет покурить на этот период — загрузит сам себя в очередь по таймеру. Там снова проверит и так далее. Это простейший вариант.

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

Решение проблемы — добавление еще одной очередной цепочки, на этот раз уже на доступ к ресурсу. Чтобы он не простаивал вообще. Т.е. один выскочил, тут же второй, третий и так далее пока все процессы не справят свою нужду в какой нибудь там USART.
Недостаток очевиден — еще одна очередь это дополнительная память, дополнительный код, дополнительное время. Можно, конечно, извратиться и на очередь к вектору натравить код диспетчера основной цепи. Но тут надо все внимательно отлаживать, ведь вызываться он будет по прерыванию! Да и громоздко, требуется лишь тогда, когда у нас много желающих.

Второе решение — выкинуть переменную времени занятости, оставив только флаг «Занято!». А процесс который пытается обратиться не убегает покурить, а отскакивает на пару шагов назад — на конец очереди задач и сразу же ломится обратно. Народ вокруг сортира не вокруг бегает, а толкется локтями у входа по принципу кто первый пролезет.
Недостаток другой — большая нагрузка на главный конвеер, куча запросов на постановку в очередь так недолго распухнуть на всю оперативку и повстречаться со стеком, а это черевато глобальным апокалипсисом.

Разумеется таймер тут приведен для примера, большую часть задач можно решить системным таймером RTOS, но если нужна вдруг меньшая дискретность или высокая скорость реакции на событие (а не пока главный конвеер дотащит задачу до исполнения), то механим управляемых прерываний, ИМХО, то что доктор прописал.