Avr на c – просто? часть 3

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».

  1. #include //Библиотека ввода/вывода

  2. #include //Библиотека прерываний

  3. #define RS 2 //Вывод ЖКИ RS подключаем на вывод PD2 МК

  4. #define E 3 //Вывод ЖКИ E подключаем на вывод PD3 МК

  5. #define TIME<\p>

  6. //Временная константа для ЖКИ

  7. //Тактовая частота 4Mhz

  8. unsigned char status=0;

  9. //Переменная статуса

  10. //Программа задержки

  11. void pause (unsigned int a)<\p>

  12. {<\p>

  13. unsigned int i; //декларация переменной для программы задержки

  14. for (i=a;i>0;i–); //Основной цикл программы задержки

  15. }

  16. //Передача команды в ЖКИ<\p>

  17. void lcd_com (unsigned char lcd) //Программа записи команд в ЖКИ<\p>

  18. {<\p>

  19. unsigned char temp;

  20. 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 и выглядит вот так (нажмите на картинку, чтобы увеличить):

Наш микроконтроллер работает по простому алгоритму:

  1. Считывает из флеш памяти UFM очередную команду.
  2. Декодирует команду и выбирает для нее нужные операнды из регистров или непосредственно из кода команды.
  3. Выполняет команду в арифметико-логическом устройстве.
  4. Запоминает результат исполнения команды в регистре приемнике, определяемом командой.
  5. Переходит к исполнению следующей команды.

У нас сейчас нет цели сделать высокопроизводительный микроконтроллер, мы не будем делать конвейерную обработку данных. Это объясняется тем, что команды из флеш памяти чипа мы можем считывать только в последовательном формате, то есть на чтение одной команды нужно как минимум 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

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}
");let k=document.querySelector(".flat_pm_modal[data-id-modal=\""+a.ID+"\"]");if(-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(k,d):jQuery(k).html(b+d),"px"==a.how.popup.px_s)e.bind(h,()=>{e.scrollTop()>a.how.popup.after&&(e.unbind(h),f.unbind(i),j())}),void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{e.unbind(h),f.unbind(i),j()});else{let b=setTimeout(()=>{f.unbind(i),j()},1e3*a.how.popup.after);void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{clearTimeout(b),f.unbind(i),j()})}f.on("click",".flat_pm_modal .flat_pm_crs",()=>{jQuery.arcticmodal("close")})}if(void 0!==a.how.outgoing){let b,c="0"==a.how.outgoing.indent?"":" style=\"bottom:"+a.how.outgoing.indent+"px\"",e="true"==a.how.outgoing.cross?"":"",f=jQuery(window),g="scroll.out"+a.ID,h=void 0===flatPM_getCookie("flat_out_"+a.ID+"_mb")||"false"!=flatPM_getCookie("flat_out_"+a.ID+"_mb"),i=document.createElement("div"),j=jQuery("body"),k=()=>{void 0!==a.how.outgoing.cookie&&"false"==a.how.outgoing.cookie&&h&&(jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show"),j.on("click",".flat_pm_out[data-id-out=\""+a.ID+"\"] .flat_pm_crs",function(){flatPM_setCookie("flat_out_"+a.ID+"_mb",!1)})),(void 0===a.how.outgoing.cookie||"false"!=a.how.outgoing.cookie)&&jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show")};switch(a.how.outgoing.whence){case"1":b="top";break;case"2":b="bottom";break;case"3":b="left";break;case"4":b="right";}jQuery("body > *").eq(0).before("
"+e+"
");let m=document.querySelector(".flat_pm_out[data-id-out=\""+a.ID+"\"]");-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(m,d):jQuery(m).html(e+d),"px"==a.how.outgoing.px_s?f.bind(g,()=>{f.scrollTop()>a.how.outgoing.after&&(f.unbind(g),k())}):setTimeout(()=>{k()},1e3*a.how.outgoing.after),j.on("click",".flat_pm_out .flat_pm_crs",function(){jQuery(this).parent().removeClass("show").addClass("closed")})}countMode&&(flat_count["block_"+a.ID]={},flat_count["block_"+a.ID].count=1,flat_count["block_"+a.ID].click=0,flat_count["block_"+a.ID].id=a.ID)}catch(a){console.warn(a)}}function flatPM_start(){let a=flat_pm_arr.length;if(0==a)return flat_pm_arr=[],void jQuery(".flat_pm_start, .flat_pm_end").remove();flat_body=flat_body||jQuery("body"),!flat_counter&&countMode&&(flat_counter=!0,flat_body.on("click","[data-flat-id]",function(){let a=jQuery(this),b=a.attr("data-flat-id");flat_count["block_"+b].click++}),flat_body.on("mouseenter","[data-flat-id] iframe",function(){let a=jQuery(this),b=a.closest("[data-flat-id]").attr("data-flat-id");flat_iframe=b}).on("mouseleave","[data-flat-id] iframe",function(){flat_iframe=-1}),jQuery(window).on("beforeunload",()=>{jQuery.isEmptyObject(flat_count)||jQuery.ajax({async:!1,type:"POST",url:ajaxUrlFlatPM,dataType:"json",data:{action:"flat_pm_ajax",data_me:{method:"flat_pm_block_counter",arr:flat_count}}})}).on("blur",()=>{-1!=flat_iframe&&flat_count["block_"+flat_iframe].click++})),flat_userVars.init();for(let b=0;bflat_userVars.textlen||void 0!==a.chapter_sub&&a.chapter_subflat_userVars.titlelen||void 0!==a.title_sub&&a.title_subc&&cc&&c>d&&(b=flatPM_addDays(b,-1)),b>e||cd||c-1!=flat_userVars.referer.indexOf(a))||void 0!==a.referer.referer_disabled&&-1!=a.referer.referer_disabled.findIndex(a=>-1!=flat_userVars.referer.indexOf(a)))&&(c=!0),c||void 0===a.browser||(void 0===a.browser.browser_enabled||-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser))&&(void 0===a.browser.browser_disabled||-1==a.browser.browser_disabled.indexOf(flat_userVars.browser)))){if(c&&void 0!==a.browser&&void 0!==a.browser.browser_enabled&&-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser)&&(c=!1),!c&&(void 0!==a.geo||void 0!==a.role)&&(""==flat_userVars.ccode||""==flat_userVars.country||""==flat_userVars.city||""==flat_userVars.role)){flat_pm_then.push(a),flatPM_setWrap(a),flat_body.hasClass("flat_pm_block_geo_role")||(flat_body.addClass("flat_pm_block_geo_role"),flatPM_ajax("flat_pm_block_geo_role")),c=!0}c||(flatPM_setWrap(a),flatPM_next(a))}}}let b=jQuery(".flatPM_sticky");b.each(function(){let a=jQuery(this),b=a.data("height")||350,c=a.data("top");a.wrap("
");let d=a.parent()[0];flatPM_sticky(this,d,c)}),debugMode||countMode||jQuery("[data-flat-id]:not([data-id-out]):not([data-id-modal])").contents().unwrap(),flat_pm_arr=[],jQuery(".flat_pm_start, .flat_pm_end").remove()}

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

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

Что нам не хватает ?  А не хватает в первую очередь стабильности генерируемых длительностей сигналов, помните заломы вертикальной линии ?второе, нам необходимо генерировать динамичное содержимое – иначе зачем мы все это затеяли,ну и третье – желательно одновременно с генерацией изображения еще и делать какую то полезную работу – иначе нам просто нечего выводить на экран – выводить статические картинки надоест очень быстро Итак, в прошлой части статьи мы определили длительности сигналов необходимые для генерации кадровой синхронизирующей последовательностикак ее генерировать?Есть несколько вариантов, и все они реализуемы на языке ассемблера:

Вариант 1:
Делаем генерацию вручную, считаем по тактам сколько длиться тот или иной цикл задержки и выдаем значения в порты ввода вывода..

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

Вариант 2:
Делаем генерацию синхросигналов при помощи таймера !

У нас есть 16ти битный таймер T1, который мы можем запустить с тактовой частотой процессора в 16 мгц.Тогда один такт таймера будет отмерять временной промежуток в 116000000=0.0625 мксВ синхропоследовательностях минимальный промежуток который мы должны выдавать на телевизор это 2.35 мксв тактах таймера это будет: 2.35/0.0625=37.6=38 тактов!остальные импульсы большей длительности

Причем самое важное что мы генерацию импульса можем возложить полностью на таймер T1, так как у него есть специальный вывод OC1A который может сам изменять свое значение по совпадению значения текущего счетчика TCNT1 со значением регистра сравнения OCR1A
Очень важно, что сигнал на OC1A будет изменяться без кода ! автоматически как только значения в регистре TCNT1 которое увеличивается на 1 с каждым тактом таймера совпадет со значением OCR1A. Так же не менее для нас важна возможность задания сброса значения счетчика TCNT1 после его совпадения с OCR1A, ведь как только значение на выходе OC1A изменилось начинается отсчет нового периода, и нам не придется высчитывать задержки из-за выполняемого кода

Далее, напрашивается решение с записью всех нужных длительностей сигналов в массив с последовательной выборкой значений и записью их в OCR1A при каждом прерывании по совпадению TCNT1=OCR1A

В общем то простой алгоритм,напишем инициализацию таймера:

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

Согласитесь все достаточно просто и локанично (а еще я всегда пишу более менее подробные комментарии)Теперь то ради чего мы все это настраивали – генерация кадровых синхроимпульсов:
Алгоритм проще некуда!

Берем из переменной avrtv_ksiN  номер генерируемой фазы сигнала

если этот номер менее 32 (у нас 32 фазы генерируемого импульса от 0..

31) – то загружаем значение задержки и выходим из прерывания в ожидании следующего прерывания с заданной нами задержкойа теперь сформируем массив значений импульсов синхронизации (вы помните какие импульсы должны быть и в каком порядке следовать?):Каждая фаза импульса закодирована двумя байтами H (старшим) и L (младшим)Соответственно фаза Z – это уровень синхроимпульса (0 вольт), фаза B – это уровень черного  (0.3 вольта)И отдельно, мы определяем константы фаз импульсов:Длительность короткого импульса я увеличил до 4 мс – в принципе есть возможность уменьшить до 2.75 мкс – быстродействия генерирующего прерывания хватит, но на всех протестированных мною телевизорах длительность в 4 мкс работала стабильно, поэтому я пока остановился на такой длительности.После генерации серии коротких, длинных, коротких импульсов мы начинаем генерацию  строк изображения. А строка изображения всегда начинается со строчного синхроимпульса длинной в 4.7 мкс (вы это должны были узнать из второй части статьи)

Для этого мы введем переменную avrtv_phase которая будет флагом необходимости передачи ССИ когда переменная равна нулю – будем генерировать ССИ, иначе выдаем данные строки

Соответственно, если в прошлом прерывании передавали строчный СИ, то в повторном заходе передаем строку, это длительность уровня черного-белого 64 мкс – 4.7 мкс = 59.3 мкс

теперь нам опять нужно спланировать вывод информации в строках кадра.

Всего у нас в кадре 288 строк изображения, но

после вывода кадровой синхронизирующей последовательности обязательно выдать не менее 17 пустых строк (см. вторую часть статьи), потом, нам для вывода 28 строк символов по 8 точек нужно будет вывести 28 * 8 = 224 строки изображения.. и потом опять вывести пустые строки. до общего количества строк.Чтобы не высчитывать на калькуляторе я переложил эту работу на компилятор:

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

Теперь код в котором генерируются строки изображения :
Теперь интересный момент – переход к выдаче данных строки.

Сначала я хотел делать программную задержку в 8 мкс перед началом генерации строки изображения, но в этом случае код становился сложноватым, плюс ко всему из-за возможных задержек в 1-5 тактов при входе в прерывание изображение строки могло быть сдвинуто на 1-2 пиксела по горизонтали от прошлых строк (пиксел на телевизоре строиться за 2 такта в 16 мгц, следовательно 4 такта задержки при входе в прерывание – 2 точки по горизонтали сдвига)

Выход нашелся можно сказать сам собой: запустим на счетчике T1 сравнение по регистру OCR1B, в который и загрузим ожидаемую длительность в 8 мкс.

Я специально написал тестовую программу которая проверила – при настройках таймера CTC top=OCR1A и включенных прерываниях, можно использовать и прерывания по совпадению по регистру OCR1B, при этом сброса регистра TCNT1
cчетчика не происходит.

Главное следить чтобы значение OCR1B было меньше OCR1A иначе условие TCNT1=OCR1B не будет выполнено ни разу, так как TCNT1 будет сбрасываться в ноль при каждом совпадении с OCR1A. В нашей реализации OCR1A (59.3 мкс) всегда больше OCR1B (8 мкс).

Внимательные наверное заметили еще одну тонкость: дело в том что после того как мы включили прерывания по OCR1B мы разрешили прерывания (хотя сами находимся в прерывании) и затем подали команду sleep – которой остановили процессор до возникновения прерывания, причем ждем мы прерывания по OCR1B=TCNT1

данная конструкция необходима потому что мы точно знаем что возникшее прерывание по OCR1B будет каждый раз прерывать команду sleep – и соответственно у нас не будет расхождений по моменту начала выдачи строк..
В момент генерации прерывания все прерывания запрещаются, командой sei мы явно показали что нас можно (и нам даже нужно!) прерывать… Генерация пустых строк после вывода строк изображения такая же как и пустых строк после, в качестве домашнего задания разберите этот блок самостоятельно, если что не понятно пишите вопросы в комментариях – я поясню.Вот и вся генерация синхроимпульсов и позиционирование выводимых строк на экране телевизора. Надеюсь Вам все понятно и не кажется сложным.

Теперь можно переходить к следующей  части:

Генерация видеосигнала при помощи контроллеров AVR. Часть 4. Генерация изображения в строке

p.s. скачать исходник библиотеки можно из форума http://vg.ucoz.ru/forum/9-18-1