AVR на C – просто? Часть 3
ШИМ (широтно импульсная модуляция) в микроконтроллере реализуется с использованием аппаратных таймеров. Теорию ШИМ рассматривать не будем. Рассмотрим лишь получение плавного изменения напряжения/тока на основе ШИМ. Для начала разберемся как управлять таймерами.
Имеется три таймера два по 8 бит (TC0, TC2) и один 16 бит (TC1). Настройка происходит регистрами TCCRxA, TCCRxB(x – означает выбранный таймер 0, 1 или 2).
Разберем чуточку подробнее на примере таймера TC1 в режиме 8 бит:
Регистр TCCR1A
бит | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
COM1A1 | COM1A0 | COM1B1 | COM1B0 | – | – | WGM11 | WGM10 |
Бит 7, 6 (COM1A1, COM1A0)- настройка поведения вывода A по событию.
Бит 5, 4 (COM1B1, COM1B0)- настройка поведения вывода B по событию.
COM1A1/COM1B1 | COM1A0/COM1B0 | События |
Нормальная работа порта, таймер отключен от вывода. | ||
1 | Переключение порта с 0 на 1 или обратно при совпадении. | |
1 | Сброс на 0 при совпадении. | |
1 | 1 | Установка 1 при совпадении. |
Бит 3, 2 — зарезервированы.
Бит 1, 0 (WGM11, WGM10) — позволяют настроить ШИМ, подробно рассматривать не будем из-за объемности описания.
Регистр TCCR1B
бит | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
FOC1A | FOC1B | – | – | WGM12 | CS12 | CS11 | CS10 |
Бит 7, 6 (FOC1A, FOC1B)- при использовании ШИМ должны быть установлены в 0.
Бит 5, 4 — зарезервированы.
Бит 3 — используется совместно с WGM11, WGM10 для настройки ШИМ.
Бит 2, 1, 0 (CS12, CS11, CS10) — выбор источника тактирования таймера.
CS12 | CS11 | CS10 | Событие |
Нет тактового сигнала (таймер остановлен) | |||
1 | CLK | ||
1 | CLK/8 | ||
1 | 1 | CLK/64 | |
1 | CLK/256 | ||
1 | 1 | CLK/1024 | |
1 | 1 | Внешний тактовый импульс по заднему фронту. | |
1 | 1 | 1 | Внешний тактовый импульс по переднему фронту. |
Регистр TCNT1
бит | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
– | – | – | – | – | – | – | – |
Из регистра TCNT1 можно считать значение или записать начальное значение для таймера.
Регистры OCN1A и OCN1B
бит | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
– | – | – | – | – | – | – | – |
Регистры OCN1A и OCN1B содержат значение сравнения связанные с соответствующими выводами микроконтроллера.
Есть еще два регистра TIMSK1 и TIFR1 – они предназначены для настройки маски прерываний и флагов прерываний.
Все перечисленные регистры можно использовать для всех трех таймеров. Для таймера TC1 есть возможность использовать расширенные настройки в связи с его 16-битной структурой.
Данный раздел не претендует на полноту описания работы с таймерами и ШИМ.
3.1. Программа с ШИМ
Для начала построим в Proteus схему и дальше применим ее для моделирования
Теперь программа.
#include //Подключаем библиотеку ввода/вывода AVR
#include //Подключаем библиотеку формирования задержки выполнения int main(void)
{ DDRB |= 1
Источник: http://cxem.net/mc/mc413.php
AVR. Учебный Курс. Программирование на Си. Часть 3
Фоновую программку мы сделали, интерфейс связи с компом инициализировали, надо бы сделать так, чтобы наш контроллер мог принимать сигналы от компа. Проще всего это сделать на прерываниях.
Что такое прерывание?
Прерывание это аппаратное событие, например, байт пришел в порт, на выводе изменился логический уровень, АЦП обсчитала напряжение или таймер дотикал до переполнения. В общем, любой аппаратный сигнал. Когда сигнал приходит, то периферийный блок в своем регистре поднимет флаг прерывания.
Когда приходит прерывание то контроллер завершит текущую команду (машинную инструкцию!) сразу же кинется выполнять процедуру обработки прерывания, а как выполнит, то вернется к прерваной фоновой программе.
https://www.youtube.com/watch?v=XsfU3HoahQM
Прерывания можно, а часто необходимо запрещать, чтобы посреди критичного участка не ускакать выполнять невесть что. Запрещать их можно глобально, флагом I в регистре SREG, а можно локально — запрещая источник каждого прерывания индивидуально. По дефолту, при сбросе, все прерывания от устройств запрещены, глобальный флаг тоже сброшен. Включем мы их по мере надобности.
Поскольку прерывание приходит ВНЕЗАПНО, а у нас могут быть несохраненные данные, то обработчик их должен сохранить и при выходе в фоновую программу вернуть все как было.
Впрочем, если бездумно подходить к этому делу, то можно огрести адские хаотичные глюки.
Особенно при использовании высокоуровневых языков вроде Си, где вся эта процедура скрыта от глаз программиста и если он не волокет в асме и не понимает работу контроллера на уровне машинных инструкций, то ошибку найти не сможет.
Но об этом чуть позже, когда буду расписывать отладку и ошибки.
Вектора прерываний
Как процессор узнает куда ему перепрыгивать? А по вектору! Помнишь я тебе показывал на таблицу векторов прерываний? Она в самом начале памяти идет. Вот это оно.
Вектор это адрес перехода. У каждого аппаратного события имеющего прерывание есть свой вектор. Аппаратных событий у AVR тьма, поэтому таблица прерываний весьма толстая, десятки адресов.
Когда происходит прерывание, то ядро контроллера запоминает текущий адрес в стеке и делает прыжок на адрес вектора соответствующего прерывания. И все. Дальше наша забота — в ячейку памяти на которую указывает вектор прерывания мы вписываем свою команду RJMP которая перенаправит проц на реальный код обработчика.
Приоритеты прерываний
Тут принцип просто — кто раньше встал того и тапки. Когда процессор уходит на обработку прерывания, то аппаратно происходит запрет всех остальных прерываний.
По выходу из обработчика, по команде RETI флаг I вернется в прежнее состояние.
У многих сразу возникнет вопрос, а что будет если во время обработки одного прерывания придет другое? Оно потеряется?
Нет, не потеряется — у него взведется флаг и как только мы завершим первый обработчик автоматом произойдет переход к отложенному прерыванию. Единственное, что мы можем потерять количество одинаковых прерываний. Т.е. если обрабатывается, например, INT0 и прерывания запрещены, а в это время придет три раза INT1, то на выходе INT1 обработается только один раз.
А если во время обработки первого прерывания случится не одно, а, скажем, два или три? Какое из этих отложеных прерываний будет выполнено первым? А по порядку в таблице векторов. У кого адрес младше тот и пойдет первым.
Теперь тезисно:
- Прерывания это аппаратные события.
- У каждого прерывания есть свой персональный адрес вектор, по которому и будет послан проц в случае чего.
- По дефолту они запрещены локально и глобально.
- Вызов прерывания может быть когда угодно и где угодно, между любыми двумя командами (хотя хрена там, между CLI CLI его не будет 🙂 )
- В обработчике прерывания надо учитывать тот факт, что данные нужно сохранять и восстанавливать при выходе.
- Приоритет прерываний работает по принципу «кто первый встал тот и ходит в тапках». Остальные запрещены аппаратно, но можно разрешить программно уже в обработчике.
- Проснувшиеся после никуда не теряются и ждут своих тапок — права порулить процом, в порядке жесткой очереди по таблице прерываний.
- Прерывания это колоссальный источник глюков и головная боль любого быдлокодера. Но без них никак, поэтому эту тему надо знать вдоль и поперек.
- Посколькоу прерывание ВНЕЗАПНОЕ и может быть где угодно, то обработчик прерывания должен выполняться МАКСИМАЛЬНО КОРОТКО И БЫСТРО. Зашел, отметился, вышел. Только так. Никаких задержек и длинных циклов. Все сложные вещи только в фоновой задаче. Но об этом подробней позже.
Перечитать три раза. Запомнить навсегда.
Когда пишешь на Си, то прервания можно использовать только по назначению. Ассемблерщики же могут извращаться, например, для создания уберточного кода когда нужно выдерживать длительность с точностью до такта. .
Итак, теорию повторили, вернемся к практике.
Прерывание у нас будет от USART. Пришел байт — вызвалось прерывание.
Раз мы решили заюзать прерывания, то сначала надо подключить библиотеку прерываний:
1 | #include |
#include<\p>
Оформляется прерывание в WinAVR как:
1 2 3 |
ISR(вектор прерывания) { } |
ISR(вектор прерывания) { }
Так что берем и добавляем в код, где нибудь до или после процедуры main эту бодягу. Вот только как узнать что вписать в вектор прерывания? Мануалы к черту — считаем что их нет. Инфу будем добывать раскопками, выгрызая из подножного корма.
Прерывания вещь интимная и зависят от конкретной модели контроллера. Так что искать описание векторов прерываний надо искать в файле который описывает наш контроллер.
https://www.youtube.com/watch?v=JUy8tAjBvhc
Какой это файл? А позырь в дерево проектов, ветка зависимостей. Что там? У меня Mega16 и там есть файл iom16.h Из всех файлов он больше всех похож на искомый — потому как только он явно для меги16 =).
Там куча всего, что искать? А все что связано с прерываниями — ищи Interrupt и все что рядом.
Очень скоро найдешь секцию /* Interrupt vectors */ и там будут все вектора, в том числе и на USART
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
/* Interrupt vectors */ /* Vector 0 is the reset vector. */ /* External Interrupt Request 0 */ #define INT0_vect _VECTOR(1) #define SIG_INTERRUPT0 _VECTOR(1) /* External Interrupt Request 1 */ #define INT1_vect _VECTOR(2) #define SIG_INTERRUPT1 _VECTOR(2) /* Timer/Counter2 Compare Match */ #define TIMER2_COMP_vect _VECTOR(3) #define SIG_OUTPUT_COMPARE2 _VECTOR(3) /* Timer/Counter2 Overflow */ #define TIMER2_OVF_vect _VECTOR(4) #define SIG_OVERFLOW2 _VECTOR(4) /* Timer/Counter1 Capture Event */ #define TIMER1_CAPT_vect _VECTOR(5) #define SIG_INPUT_CAPTURE1 _VECTOR(5) /* Timer/Counter1 Compare Match A */ #define TIMER1_COMPA_vect _VECTOR(6) #define SIG_OUTPUT_COMPARE1A _VECTOR(6) /* Timer/Counter1 Compare Match B */ #define TIMER1_COMPB_vect _VECTOR(7) #define SIG_OUTPUT_COMPARE1B _VECTOR(7) /* Timer/Counter1 Overflow */ #define TIMER1_OVF_vect _VECTOR(8) #define SIG_OVERFLOW1 _VECTOR(8) /* Timer/Counter0 Overflow */ #define TIMER0_OVF_vect _VECTOR(9) #define SIG_OVERFLOW0 _VECTOR(9) /* Serial Transfer Complete */ #define SPI_STC_vect _VECTOR(10) #define SIG_SPI _VECTOR(10) /* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13) /* ADC Conversion Complete */ #define ADC_vect _VECTOR(14) #define SIG_ADC _VECTOR(14) /* EEPROM Ready */ #define EE_RDY_vect _VECTOR(15) #define SIG_EEPROM_READY _VECTOR(15) /* Analog Comparator */ #define ANA_COMP_vect _VECTOR(16) #define SIG_COMPARATOR _VECTOR(16) /* 2-wire Serial Interface */ #define TWI_vect _VECTOR(17) #define SIG_2WIRE_SERIAL _VECTOR(17) /* External Interrupt Request 2 */ #define INT2_vect _VECTOR(18) #define SIG_INTERRUPT2 _VECTOR(18) /* Timer/Counter0 Compare Match */ #define TIMER0_COMP_vect _VECTOR(19) #define SIG_OUTPUT_COMPARE0 _VECTOR(19) /* Store Program Memory Ready */ #define SPM_RDY_vect _VECTOR(20) #define SIG_SPM_READY _VECTOR(20) #define _VECTORS_SIZE 84 |
/* Interrupt vectors */ /* Vector 0 is the reset vector.
*/ /* External Interrupt Request 0 */ #define INT0_vect _VECTOR(1) #define SIG_INTERRUPT0 _VECTOR(1) /* External Interrupt Request 1 */ #define INT1_vect _VECTOR(2) #define SIG_INTERRUPT1 _VECTOR(2) /* Timer/Counter2 Compare Match */ #define TIMER2_COMP_vect _VECTOR(3) #define SIG_OUTPUT_COMPARE2 _VECTOR(3) /* Timer/Counter2 Overflow */ #define TIMER2_OVF_vect _VECTOR(4) #define SIG_OVERFLOW2 _VECTOR(4) /* Timer/Counter1 Capture Event */ #define TIMER1_CAPT_vect _VECTOR(5) #define SIG_INPUT_CAPTURE1 _VECTOR(5) /* Timer/Counter1 Compare Match A */ #define TIMER1_COMPA_vect _VECTOR(6) #define SIG_OUTPUT_COMPARE1A _VECTOR(6) /* Timer/Counter1 Compare Match B */ #define TIMER1_COMPB_vect _VECTOR(7) #define SIG_OUTPUT_COMPARE1B _VECTOR(7) /* Timer/Counter1 Overflow */ #define TIMER1_OVF_vect _VECTOR(8) #define SIG_OVERFLOW1 _VECTOR(8) /* Timer/Counter0 Overflow */ #define TIMER0_OVF_vect _VECTOR(9) #define SIG_OVERFLOW0 _VECTOR(9) /* Serial Transfer Complete */ #define SPI_STC_vect _VECTOR(10) #define SIG_SPI _VECTOR(10) /* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13) /* ADC Conversion Complete */ #define ADC_vect _VECTOR(14) #define SIG_ADC _VECTOR(14) /* EEPROM Ready */ #define EE_RDY_vect _VECTOR(15) #define SIG_EEPROM_READY _VECTOR(15) /* Analog Comparator */ #define ANA_COMP_vect _VECTOR(16) #define SIG_COMPARATOR _VECTOR(16) /* 2-wire Serial Interface */ #define TWI_vect _VECTOR(17) #define SIG_2WIRE_SERIAL _VECTOR(17) /* External Interrupt Request 2 */ #define INT2_vect _VECTOR(18) #define SIG_INTERRUPT2 _VECTOR(18) /* Timer/Counter0 Compare Match */ #define TIMER0_COMP_vect _VECTOR(19) #define SIG_OUTPUT_COMPARE0 _VECTOR(19) /* Store Program Memory Ready */ #define SPM_RDY_vect _VECTOR(20) #define SIG_SPM_READY _VECTOR(20) #define _VECTORS_SIZE 84
Прорва, на каждый чих, но нам сейчас интересны те прерывания которые отвечают за USART
Вот они, все три. Одна завершение приема, вторая опустошение регистра ака готовность к передаче следующего байта, третья завершение передачи.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13) |
/* USART, Rx Complete */ #define USART_RXC_vect _VECTOR(11) #define SIG_USART_RECV _VECTOR(11) #define SIG_UART_RECV _VECTOR(11) /* USART Data Register Empty */ #define USART_UDRE_vect _VECTOR(12) #define SIG_USART_DATA _VECTOR(12) #define SIG_UART_DATA _VECTOR(12) /* USART, Tx Complete */ #define USART_TXC_vect _VECTOR(13) #define SIG_USART_TRANS _VECTOR(13) #define SIG_UART_TRANS _VECTOR(13)
Мы собрались по входному сигналу делать экшн. Так что применяй первый. Какой из трех? Без разницы, они равнозначны и их тут столько исключительно из совместимости с разными версиями компиляторов.
Вписываешь в код вот такую шнягу:
1 2 3 4 |
ISR(USART_RXC_vect) { } |
ISR(USART_RXC_vect) { }
Осталось прописать в код то что мы должны сделать из прерывания. Первое — нужно обязательно считать данные из регистра UDR иначе флаг прерывания останется висеть и оно будет непрерывно вызываться еще и еще.
Для определения байта можно применить конструкцию Switch-case
В итоге, получилась такая вещь:
1 2 3 4 5 6 7 8 9 |
ISR(USART_RXC_vect) { switch(UDR) { case '1': LED_PORT = 1 |
Источник: http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-chast-3.html
Работа с кнопками программа обработки нажатия для микроконтроллера ATmega8
В этой заметке пойдет речь о считывании информации с помощью клавиш. Выводить введенную информацию будем на алфавитно-символьный ЖКИ. Предлагаю начать с самого простого. Подсоединим одну клавишу, без фиксатора, нормально разомкнутую и рассмотрим некоторые тонкости работы.
В этой заметке пойдет речь о считывании информации с помощью клавиш. Выводить введенную информацию будем на алфавитно-символьный ЖКИ. Предлагаю начать с самого простого. Подсоединим одну клавишу, без фиксатора, нормально разомкнутую и рассмотрим некоторые тонкости работы.
Испытуемая клавиша А2 при нажатии закорачивает вывод PB0 на «землю». Вся остальная обвязка стандартна.
Первой тонкостью работы является дребезг контактов, неизбежный при использовании не специализированных кнопок. Его можно убрать с помощью таймера LM555, но это извращение, если мы собрались использовать микроконтроллер.
Второй тонкостью является вопрос:
При появлении логического нуля на выводе порта микроконтроллера на протяжении 1мс? Но при залипании клавиши может возникнуть неприятная ситуация.
Третьим будет вопрос,
«с какой периодичностью проверять клавиши?»
Так как реакция человека на внешние раздражители редко когда превышает 0,03с. То условимся считывать состояние клавиатуры 100 раз в секунду.
Клавишу будем считать нажатой, тогда, когда на порту микроконтроллера в течении более чем 0,01с будет присутствовать низкий уровень, после чего клавиша будет отжата (на порту микроконтроллера появится единица).
В связи с этими рассуждениями напишем программу, которая будет обрабатывать нажатие одной клавиши, имитируя включение чего-то, на ЖКИ соответственно будем писать «On», «Off».
-
#include //Библиотека ввода/вывода
-
#include //Библиотека прерываний
-
#define RS 2 //Вывод ЖКИ RS подключаем на вывод PD2 МК
-
#define E 3 //Вывод ЖКИ E подключаем на вывод PD3 МК
-
#define TIME<\p>
-
//Временная константа для ЖКИ
-
//Тактовая частота 4Mhz
-
unsigned char status=0;
-
//Переменная статуса
-
//Программа задержки
-
void pause (unsigned int a)<\p>
-
{<\p>
-
unsigned int i; //декларация переменной для программы задержки
-
for (i=a;i>0;i–); //Основной цикл программы задержки
-
}
-
//Передача команды в ЖКИ<\p>
-
void lcd_com (unsigned char lcd) //Программа записи команд в ЖКИ<\p>
-
{<\p>
-
unsigned char temp;
-
temp=(lcd&~(1
Источник: https://avrlab.com/node/83
Микропаскаль учебный курс. Часть 3 :AVR devices
Почти всё то что мы рассматривали в учебном курсе ранее, справедливо не только для микропаскаля, но и для «большого» паскаля для ПК. Начиная с этой части так же будут рассматриваться некоторые специфические особенности паскаля для AVR. Но начнем мы с пары слов о пользовательских функциях и процедурах в микропаскале.
Функция или процедура это некоторый код который может быть вызван из любой части основной программы с какими либо параметрами (или без них). После того как процедура или функция отработала, управление возвращается основной программе. Между функциями и процедурами всего одно различие: Функция может возвращать какое либо значение, а процедура не может.
Функция выглядит следующим образом:
Function ИмяФункции(СписокПараметров):ТипВозвращаемогоЗначения; begin result:=ВозвращаемоеЗначение; end; |
Имя функции любое не содержащее пробелов и спецсимволов. Именно по этому имени происходит обращение к функции, поэтому для улучшения читаемости кода рекомендуется давать осмысленные имена функциям и процедурам. Список параметров — список переменных которые передаются в функцию из вне.
Тип возвращаемого значения — тип данных возвращаемого функцией значения. Функция должна обязательно возвратить значение, для этого нужно записать в переменную result возвращаемое значение. Если в теле функции нигде нет присвоения значения этой переменной, то проект даже не скомпилируется.
Вместо result можно так же использовать имя самой функции. Отдельное внимание стоит уделить списку параметров. Существует два способа передать в функцию что-либо: С использованием параметров-значений или с использованием параметров-переменных.
Рассмотрим оба варианта начиная с использования параметра-переменной:
program Funct; var p:byte; Function MyFunction(var a:byte):byte; begin a:=a+10; result:=a; end; begin DDRA:=0xFF; p:=123; PORTA:=MyFunction(p); end. |
В данном случае в функцию передается переменная и функция может изменять её! Это значит что когда программа отработает, в переменной p уже будет не 123, а 133. Если же мы уберем слово var из описания списка передаваемых параметров, то в функцию передастся всего лишь её значение переменной.
И что бы с ней не вытворяли внутри функции переменная p как была так и останется равной 123. Различие между параметрами-переменными и параметрами-значениям только в этом. Для процедур в микропаскале справедливо всё сказанное выше за исключением того что процедуры ничего не возвращают.
Немного перепишем наш пример для использования в нем не функции а процедуры:
program Proc; var p:byte; Procedure MyProcedure(var a:byte); begin a:=a+10; end; begin DDRA:=0xFF; p:=123; MyProcedure(p); PORTA:=p; end. |
Здесь у нас в процедуру передается переменная p которую изменяет тело процедуры прибавляя к её значению 10. В результате в PORTA будет выведено число 133.
Теперь можно перейти к прерываниям. Если кто не помнит что такое прерывание то напоминаю: Прерывание это некоторое событие вызываемое периферией контроллера (таймеры, ацп, EEPROM итд).
Когда такое событие наступает, (таймер отсчитал определённый промежуток времени, ацп завершил измерение напряжения, EEPROM готова к работе итд) то основная программа на время перестаёт выполняться и управление передаётся к команде хранящейся в памяти по строго определённому адресу (Называется вектор прерывания и для каждого прерывания свой). Естественно возникает вопрос как объявить обработчик прерывания в микропаскале? Делается это следующим образом. Предположим, что хотим обрабатывать прерывание по переполнению от таймера 0 на микроконтроллере Atmega32. Открываем даташит и смотрем по какому адресу расположен вектор этого прерывания. В данном случае это адрес 0x16. Теперь пишем процедуру которая и будет обработчиком прерывания:
procedure Timer0_Interrupt(); iv 0x16; begin ///Тело обработчика прерывания end; |
Название процедуры обработчика может быть произвольное. Лезть каждый раз в даташит чтобы узнать адрес вектора прерывания не совсем удобно. Поэтому у каждого вектора прерывания есть своё название на нормальном человеческом языке.
В целях переносимости кода лучше всего использовать именно их т.к. у всех контроллеров они называются одинаково, а адреса векторов вполне могут быть разными. Для того чтоб посмотреть список названий векторов прерываний можно например открыть файл ATmega32.
mpas в папке mikroPascal PRO for AVRDefs . Там мы найдем следующее:
const IVT_ADDR_RESET = 0x0000; const IVT_ADDR_INT0 = 0x0002; const IVT_ADDR_INT1 = 0x0004; const IVT_ADDR_INT2 = 0x0006; const IVT_ADDR_TIMER2_COMP = 0x0008; const IVT_ADDR_TIMER2_OVF = 0x000A; const IVT_ADDR_TIMER1_CAPT = 0x000C; const IVT_ADDR_TIMER1_COMPA = 0x000E; const IVT_ADDR_TIMER1_COMPB = 0x0010; const IVT_ADDR_TIMER1_OVF = 0x0012; const IVT_ADDR_TIMER0_COMP = 0x0014; const IVT_ADDR_TIMER0_OVF = 0x0016; const IVT_ADDR_SPI__STC = 0x0018; const IVT_ADDR_USART__RXC = 0x001A; const IVT_ADDR_USART__UDRE = 0x001C; const IVT_ADDR_USART__TXC = 0x001E; const IVT_ADDR_ADC = 0x0020; const IVT_ADDR_EE_RDY = 0x0022; const IVT_ADDR_ANA_COMP = 0x0024; const IVT_ADDR_TWI = 0x0026; const IVT_ADDR_SPM_RDY = 0x0028; |
Это и есть ни что иное как таблица векторов прерываний для Atmega32. Ну разумеется что лезть в файл каждый раз тоже ни чуть не проще чем в даташит, поэтому когда пишем procedure Timer0_Interrupt(); iv ставим пробел после iv и жмем комбинацию CTRL + Пробел и пишем IVT после этого редактор кода сам предложит дописать имя вектора прерывания.
Разумеется для работы с прерываниями надо их сначала разрешить. Чтоб их разрешить нужно установить бит I в регистре SREG. Работать с отдельными битами регистра в микропаскале очень просто. Например чтоб установить бит I (седьмой по счёту) нужно написать SREG.B7:=1 или что еще понятней SREG_I_bit := 1;.
Для того чтоб получше себе представить как работать с прерываниями вот небольшой пример (мигание светодиодом):
program Blinker; procedure Timer0_Interrupt(); iv IVT_ADDR_TIMER0_OVF; begin PORTA:=NOT PORTA; ///Инвертируем состояние порта end; begin SREG_I_bit := 1; //Разрешаем прерывания TCCR0.CS00:=1; //Включаем таймер0 TCCR0.CS02:=1; //с делителем на 1024 TIMSK.TOIE0:=1; //Разрешаем прерывание по переполнению DDRA:=0xFF; //Порт А выход end. |
А сейчас когда с прерываниями более менее разобрались, стоит поговорить об ассемблерных вставках. В микропаскале есть возможность вставлять фрагменты ассемблерного кода в любое место программы. Делается это что-бы например сократить время выполнения особо критичных кусков кода или их размер. Ассемблерные вставки оформляются следующим образом:
program Blinker; begin DDRA:=0xFF; //Порт на выход while(TRUE) do ///Бесконечный цикл Begin asm ldi R16,0xFF out PORTA,R16 //Выводим 0xFF в порт end; delay_ms(500); // Ждем asm ldi R16,0x00 out PORTA,R16 //Выводим 0x00 в порт end; delay_ms(500); // Ждем end; end. |
Однако ассемблерных вставок стоит избегать, и использовать их в случае крайней необходимости, т.к. это очень сильно влияет на переносимость кода с одного микроконтроллера на другой. Кстати если требуется вставить в код пустую команду NOP то сделать это можно без использования всяких вставок (просто пишем NOP; и всё).
Иногда может возникнуть необходимость использовать в ассемблерной вставке какую либо переменную. Компилятор располагает все переменные по умолчанию в оперативной памяти, и адрес любой из них можно выяснить в окне Statistics на вкладке Variables.
Если для удобства хочется чтобы компилятор разметил переменную в регистре, то нужно явно сказать ему об этом при объявлении переменной:
Но и тут есть некоторые недостатки. Размещать переменные в регистрах он начинает с регистра R2. А как известно регистры R0-R15 в AVR являются немного ущербными (с ними не работают некоторые команды например ldi) .
Заставить компилятор хранить переменную в конкретном регистре мне не удалось. Кстати нужно быть аккуратным при использовании регистров в ассемблерных вставках, мало ли чего в них положил компилятор для своих нужд.
Лучше вообще предварительно запихивать их значения в стек, а потом когда регистр нам не нужен восстанавливать значение из стека.
P.S. В следующей части пойдёт рассказ о работе с периферией контроллера (АЦП, ЕЕПРОМ, UART итд)
P.P.S. Я сейчас активно пишу научный труд под названием диплом, именно поэтому с обновлениями возникают задержки. Но это временное явление 🙂
Источник: http://avrdevices.ru/mikropaskaly-utchebny-kurs-tchasty-3/
Проекты Altera Quartus II для плат Марсоход, Марсоход2 и Марсоход3
Меня часто спрашивают: “Чем отличается микроконтроллер от ПЛИС?” Ну что тут можно ответить? Это как бы разные вещи… Микропроцессор последовательно выполняет команды, описанные в его программе. Работа ПЛИС в конечном счете определяется принципиальной электрической схемой, реализованной внутри чипа.
Архитектура микроконтроллера, то есть тип процессора, количество портов ввода вывода, интерфейсы, определяется производителем. Микросхема микроконтроллера изготовлена на заводе и изменить ее нельзя. Можно только написать программу, которую он будет исполнять. ПЛИС – это свобода для творчества. Архитектура реализуемого устройства может быть почти любая, лишь бы поместилась вся логика в чип.
В ПЛИС можно, например, попробовать реализовать даже и микроконтроллер! Попробуем?
Один из самых распространенных микроконтроллеров – это 8-ми разрядные RISС процессоры семейства AVR компании Atmel. В этой статье я расскажу как реализовать “почти” совместимый с AVR микроконтроллер внутри нашей ПЛИС на плате Марсоход.
Прежде, чем начинать делать свою реализацию микроконтроллера, конечно, следует изучить внутренности контроллера AVR. Нужно как минимум знать систему команд микропроцессора AVR. На нашем сайте можно скачать его описание:
Система команд микроконтроллера AVR ( 703303 bytes )Мы не будем ставить себе целью полностью повторить поведение чипа Atmel, мы хотим сделать наш микропроцессор лишь частично совместимым. Полностью повторить можно, но нужна ПЛИС гораздо большего объема.
У нас на плате Марсоход стоит CPLD EPM240T100C5, значит у нас есть всего-навсего 240 триггеров и логических элементов.
Кроме триггеров и логики в нашей ПЛИС имеется последовательная флеш память UFM объемом 512 слов по 16 бит.
В этой флеш памяти мы будем хранить программу микроконтроллера. Удобно, что слова, хранимые во флеш, имеют разрядность 16. Все команды процессора AVR также шестнадцатиразрядные. Кое-что про UFM мы уже писали на нашем сайте.
У нас был проект для ПЛИС платы Марсоход, который выполнял чтение из UFM памяти.
“Оперативной памяти” в нашей ПЛИС нет. Ну значит не будет памяти у нашего микроконтроллера, жаль но это нас не остановит.
У микроконтроллера AVR имеется 32 восьмиразрядных регистра общего назначения. Нижняя группа регистров r0-r15 может быть использована только в командах с операндами-регистрами.
Верхняя группа регистров r16-r31 может использоваться в командах и с непосредственными операндами. Поскольку места внутри нашего чипа на плате Марсоход действительно не много, нам придется реализовать только некоторые регистры.
Это довольно существенное ограничение, и его нужно будет учитывать при написании программ для нашего микроконтроллера.
Мы реализуем только 7 регистров: r16-r22:
- Первые 4 регистра r16…r19 – это просто регистры.
- Регистр r20 – это тоже обычный регистр, только его биты мы подключим к 8-ми светодиодам платы Марсоход.
- Регистр r21 – это тоже обычный регистр, но его биты мы подключим к выводам управления шаговых двигателей на плате Марсоход.
- Регистр r22 – только для чтения. К нему подключены входы от 4-х кнопочек платы Марсоход.
Схема нашего микроконтроллера создана в среде Altera QuartusII и выглядит вот так (нажмите на картинку, чтобы увеличить):
Наш микроконтроллер работает по простому алгоритму:
- Считывает из флеш памяти UFM очередную команду.
- Декодирует команду и выбирает для нее нужные операнды из регистров или непосредственно из кода команды.
- Выполняет команду в арифметико-логическом устройстве.
- Запоминает результат исполнения команды в регистре приемнике, определяемом командой.
- Переходит к исполнению следующей команды.
У нас сейчас нет цели сделать высокопроизводительный микроконтроллер, мы не будем делать конвейерную обработку данных. Это объясняется тем, что команды из флеш памяти чипа мы можем считывать только в последовательном формате, то есть на чтение одной команды нужно как минимум 16 тактов. Быстрее здесь сделать нельзя (да нам и не нужно сейчас).
Ход выполнения программы может изменяться в зависимости от результата исполнения команд. Специальные команды переходов позволяют переходить к нужной операции в нужных условиях.
Перечислим команды микроконтроллера AVR, которые мы собираемся реализовать:
ADD 0000 11rd dddd rrrr
SUB 0001 10rd dddd rrrr
AND 0010 00rd dddd rrrr
EOR 0010 01rd dddd rrrr
OR 0010 10rd dddd rrrr
MOV 0010 11rd dddd rrrr
CP 0001 01rd dddd rrrr
LSR 1001 010d dddd 0110
SUBI 0101 KKKK dddd KKKK
ANDI 0111 KKKK dddd KKKK
ORI 0110 KKKK dddd KKKK
CPI 0011 KKKK dddd KKKK
LDI 1110 KKKK dddd KKKK
BREQ 1111 00kk kkkk k001
BRNE 1111 01kk kkkk k001
BRCS 1111 00kk kkkk k000
BRCC 1111 01kk kkkk k000
Слева написаны названия команд, а справа – их бинарное представление (кодирование). Так буква “r” обозначает регистр источник, буква “d” – регистр приемник, “K” – это непосредственно операнд.
Конечно – это только малая часть от “настоящей системы команд”, но уже и эти команды позволять писать вполне работающие программы.
У нас будет упрощенное АЛУ (Арифметико-Логическое Устройство). Оно реализует только некоторые, наиболее употребительные команды, а так же всего 2 флага для условных переходов: “Z” и “C”.
Флаг “Z” устанавливается, если результат АЛУ это ноль. Если результат из АЛУ не нулевой, то флаг “Z” сбрасывается. Флаг “C” устанавливается при возникновении переноса в арифметических операциях ADD и SUB/SUBI или сравнения CP/CPI. Флаги влияют на исполнение команд условных переходов: флаг “Z” влияет на BREQ, BRNE, а флаг “C” влияет на BRCS, BRCC.
Вообще всеь проект мы уже реализовали и его можно взять здесь:
Ядро микропроцессора Atmel AVR ( 109584 bytes ).
Исходный текст нашего ядра AVR написан на языке Verilog и его можно посмотреть здесь.
Теперь посмотрим, как мы сможем написать программу для нашего микроконтроллера? Для написания программы на языке ассемблер воспользуемся средой разработки компании Atmel AVRStudio4.
Эту среду разработки можно скачать прямо с сайта компании Атмел (после регистрации), вот здесь. Или поищите в яндексе – наверняка найдете в свободном доступе.
Создаем проект в AVRStudio4 и пишем простую программу. Программа будет моргать светодиодом на плате Марсоход и опрашивать состояние нажатых кнопочек. Если нажать одну кнопочку, то моргающий светодиод “побежит” в одну сторону, а если нажать другую кнопочку, то светодиод “побежит” в другую сторону. Вот исходный текст на ассемблере для нашего примера:
.include “1200def.inc”.device AT90S1200.cseg.org 0start:
;initial one bit in register
ldi r16,$80rd_port:
;read port (key status)
mov r17,r22 cpi r17,$0f
;go and blink one LED if no key pressed
breq do_xor cpi r17,$0e
;go and right shift LEDs if key[0] pressed
breq do_rshift cpi r17,$0d
;go and left shift LEDs if key[1] pressed
breq do_lshift
;jump to read keys
or r16,r16 brne rd_portdo_rshift: cpi r16,1 breq set80 lsr r16 mov r20,r16 brne pauseset80: ldi r16,$80 mov r20,r16 or r16,r16 brne pausedo_lshift: cpi r16,$80 breq set1 lsl r16 mov r20,r16 brne pauseset1: ldi r16,$01 mov r20,r16 or r16,r16 brne pausedo_xor: eor r20,r16pause: ldi r18,$10cycle2: ldi r19,$FFcycle1: or r19,r19 or r19,r19 subi r19,1 brne cycle1 subi r18,1 brne cycle2 or r16,r16 brne rd_port
Видите? Чтение состояния кнопочек – это чтение из регистра r22. Изменение состояния светодиодов – это запись в регистр r20.Настройте AVRStudio так, что бы выходной формат был “Generic”. Это в свойствах проекта, “Assembler Options”, настройка “Hex Output Format”.
После компиляции программы получается вот такой текстовый файл с кодами программы:
000000:e800000001:2f16000002:301f000003:f0c1000004:301e000005:f021000006:301d000007:f059000008:2b00000009:f7b900000a:300100000b:f01900000c:950600000d:2f4000000e:f47100000f:e800000010:2f40000011:2b00000012:f451000013:3800000014:f019000015:0f00000016:2f40000017:f429000018:e001000019:2f4000001a:2b0000001b:f40900001c:274000001d:e12000001e:ef3f00001f:2b33000020:2b33000021:5031000022:f7e1000023:5021000024:f7c9000025:2b00
000026:f6d1
Этот файл нам почти подходит для QuartusII. В нашем проекте для ПЛИС есть файл avr_prog.mif (Memory Initialization File), куда мы и вставляем полученный из AVRStudio код (только нужно добавить точку с запятой в конце каждой строки). Таким образом, после компиляции QuartusII эти коды попадут во флеш UFM нашей ПЛИС.
Теперь можно компилировать и пробовать наш проект в плате Марсоход. Вот видеоролик, демонстрирующий работоспособность нашего процессора:
Все работает так как и задумывалось!
Обратите внимание, что после компиляции, весь проект занимает только 205 логических элемента из 240 имеющихся в нашей ПЛИС. Это значит, что наш микроконтроллер можно и дальше усложнять или добавить какую-то новую логику. Так что проект может быть полезен для создания Ваших устройств.
Источник: https://marsohod.org/index.php/projects/66-makeavr
Генерация видеосигнала при помощи контроллеров AVR. Часть 3. Генерация синхроимпульсов на ассемблере
Источник: http://vg.ucoz.ru/publ/programmirovanie_na_assemblere_avr/3-1-0-11
Adblockdetector