AVR. Учебный Курс. Программирование на Си. Часть 3

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

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

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

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

Поскольку прерывание приходит ВНЕЗАПНО, а у нас могут быть несохраненные данные, то обработчик их должен сохранить и при выходе в фоновую программу вернуть все как было.

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

Вектора прерываний
Как процессор узнает куда ему перепрыгивать? А по вектору! Помнишь я тебе показывал на таблицу векторов прерываний? Она в самом начале памяти идет. Вот это оно.

Вектор это адрес перехода. У каждого аппаратного события имеющего прерывание есть свой вектор. Аппаратных событий у AVR тьма, поэтому таблица прерываний весьма толстая, десятки адресов.

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

Приоритеты прерываний
Тут принцип просто — кто раньше встал того и тапки. Когда процессор уходит на обработку прерывания, то аппаратно происходит запрет всех остальных прерываний.

Его, конечно, можно включить программно, установив флаг I в регистре SREG и тогда у нас будут вложенные прерывания, но обращатся с этим следует ОЧЕНЬ осторожно. Так как множественные прерывания нагружают стек и может произойти его переполнение, что даст полный сбой работы и начнется невесть что. Полная упячка.

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

У многих сразу возникнет вопрос, а что будет если во время обработки одного прерывания придет другое? Оно потеряется?

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

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

Теперь тезисно:

  • Прерывания это аппаратные события.
  • У каждого прерывания есть свой персональный адрес вектор, по которому и будет послан проц в случае чего.
  • По дефолту они запрещены локально и глобально.
  • Вызов прерывания может быть когда угодно и где угодно, между любыми двумя командами (хотя хрена там, между CLI CLI его не будет :) )
  • В обработчике прерывания надо учитывать тот факт, что данные нужно сохранять и восстанавливать при выходе.
  • Приоритет прерываний работает по принципу «кто первый встал тот и ходит в тапках». Остальные запрещены аппаратно, но можно разрешить программно уже в обработчике.
  • Проснувшиеся после никуда не теряются и ждут своих тапок — права порулить процом, в порядке жесткой очереди по таблице прерываний.
  • Прерывания это колоссальный источник глюков и головная боль любого быдлокодера. Но без них никак, поэтому эту тему надо знать вдоль и поперек.
  • Посколькоу прерывание ВНЕЗАПНОЕ и может быть где угодно, то обработчик прерывания должен выполняться МАКСИМАЛЬНО КОРОТКО И БЫСТРО. Зашел, отметился, вышел. Только так. Никаких задержек и длинных циклов. Все сложные вещи только в фоновой задаче. Но об этом подробней позже.

Перечитать три раза. Запомнить навсегда.

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

Итак, теорию повторили, вернемся к практике.

Прерывание у нас будет от USART. Пришел байт — вызвалось прерывание.

Раз мы решили заюзать прерывания, то сначала надо подключить библиотеку прерываний:

1
#include <avr/interrupt.h>

Оформляется прерывание в WinAVR как:

1
2
3
ISR(вектор прерывания)
{
}

Так что берем и добавляем в код, где нибудь до или после процедуры main эту бодягу. Вот только как узнать что вписать в вектор прерывания? Мануалы к черту — считаем что их нет. Инфу будем добывать раскопками, выгрызая из подножного корма.

Прерывания вещь интимная и зависят от конкретной модели контроллера. Так что искать описание векторов прерываний надо искать в файле который описывает наш контроллер.

Какой это файл? А позырь в дерево проектов, ветка зависимостей. Что там? У меня 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

Прорва, на каждый чих, но нам сейчас интересны те прерывания которые отвечают за 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)

Мы собрались по входному сигналу делать экшн. Так что применяй первый. Какой из трех? Без разницы, они равнозначны и их тут столько исключительно из совместимости с разными версиями компиляторов.

Вписываешь в код вот такую шнягу:

1
2
3
4
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<<LED2;	break;
	case '0': LED_PORT = 0<<LED2;	break;
	default: break;
	}
}

Как видишь, у нас в свитче берется UDR и в зависимости от того чему он равен, символу нуля или символу единицы осуществляется переход. Если не то и не другое, то переход на default и сразу выход из свитча — break. break это вообще выход из программной структуры. Например, если сделаешь break в цикле, то выпадешь из цикла.

В значении case можно было бы написать и код символа, было бы case 0x31: но, как я говорил, никаких цифр. Если есть возможность их избежать — избегай всеми силами. Пусть за тебя препроцессор компилятора отдувается.

В частности ‘символ’ это ASCII код символа. Удобно!

Да, обрати внимание на то, что в каждой строке case стоит break. Это неспроста! Если break там не будет, то пройдя на case 0 и выполнив там все процессор перейдет к следующему case и так далее, пока не выйдет из него совсем или не нарвется на break.

Что зажигания диодов, то тут, как видишь, пришлось добавить еще и LED2, прописав его в дефайнах. И не забыв проинициализировать его порт на выход! В итоге, стало выглядеть так:

1
 LED_DDR = 1<<LED1|1<<LED2;

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

Вот текущий код целиком:

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
#define F_CPU 8000000L
#define LED1 		4
#define LED2		5
#define LED_PORT 	PORTD
#define LED_DDR		DDRD
 
 
#include &lt;avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
 
ISR(USART_RXC_vect)
{
switch(UDR)
	{
	case '1': LED_PORT = 1<<LED2;	break;
	case '0': LED_PORT = 0<<LED2;	break;
	default: break;
	}
}
 
int main(void)
{
volatile unsigned char i;
 
#define XTAL 8000000L
#define baudrate 9600L
#define bauddivider (XTAL/(16*baudrate)-1)
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)
 
UBRRL = LO(bauddivider);
UBRRH = HI(bauddivider);
UCSRA = 0;
UCSRB = 1<<RXEN|1<<TXEN|1<<RXCIE|1<<TXCIE;
UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
 
 
LED_DDR = 1<<LED1|1<<LED2;
 
 
while(1)
 {
 i++;
 LED_PORT=0<<LED1;
 _delay_ms(1000);
 LED_PORT=1<<LED1; 
 _delay_ms(1000);
 }
 
return 0;
}

Вроде все написано. Запускай код на эмуляцию. Но вот как проверить прерывание?

В данном случае, в студии, для этого нет никаких эмуляторов терминала (вообще есть hapsim, а еще можно отлаживать в Proteus или VMLAB, но об этом я пожалуй расскажу попозже).

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

У USART на прием флаг RxC вот возьми и ткни его, мол прерывание свершилось.

Вручную выстави этот бит, найдя его в окне i/o view в ветви USART. Поставил, а потом сделай пару шагов по F11 и… нифига не произошло!

А почему? а потому, что мы забыли эти прерывания врубить. Частая ошибка начинающих. Дело в том, что мало разрешить прерывания при инициализации UART — это мы разрешили локальные прерывания, уартовские, а есть еще флаг глобального разрешения — флаг I он по дефолту сброшен.

Установка и сброс флага разрешения глобальных прерываний делается командой SEI и CLI соотвественно. В Си есть макрос sei(); и cli(); который просто подставит эту ассемблерную команду. Берем и добавляем его в конце инициализации:
Закопипащу немного кода оттуда, чтобы ты понял куда я его сунул:

1
2
3
4
5
UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
LED_DDR = 1<<LED1|1<<LED2;
sei();				// <--- вот оно.
 
while(1)

Перезапусти эмуляцию и снова ткни флаг, сделав после пару шагов.

Во, теперь у тебя программа должна будет оказаться в ISR cтоя на заголове Switch. Пора проверить как работает.

В этот момент открой I/O View и натыкай в регистре UDR число 0х31 или просто ткни в окошко со значением и введи там код 0х31.

Нажми F11 опа! мы перешли к первой строке case и зажгли диодик, а после сразу же вышли из кейса и из прерывания вообще.

Вскрытие прерывания
Теперь пора показать тебе как работает прерывание изнутри. Переходи в дизассемблер и смотри что там поменялось. В первую очередь, изменился переход на векторе номер 11 — вектор RXC — мы ведь на него обработчик повесили.

….
+00000014: 940C0034 JMP 0x00000034 Jump
+00000016: 940C0036 JMP 0x00000036 Jump
+00000018: 940C0034 JMP 0x00000034 Jump
….

А со строки

@00000036: __vector_11

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

1
2
3
4
5
6
+00000036:   921F        PUSH      R1             Push register on stack
+00000037:   920F        PUSH      R0             Push register on stack
+00000038:   B60F        IN        R0,0x3F        In from I/O location
+00000039:   920F        PUSH      R0             Push register on stack
+0000003A:   2411        CLR       R1             Clear Register
+0000003B:   938F        PUSH      R24            Push register on stack

По такой бодяге легко отличить обработчик прерывания. То что там затесался IN R0,0x3F это не случайно — таким образом идет сохранение регистра SREG в котором хранятся флаги. Их тоже сохранять обязательно, ведь от них зависят операции условий.

Дальше идет наш switch:
Свитч отличить тоже несложно — обычно это куча последовательных сравнений — CPI

1
2
3
4
5
6
  switch(UDR)
+0000003C:   B18C        IN        R24,0x0C     	//Берем значение из UDR 
+0000003D:   3380        CPI       R24,0x30     // И сравниваем его последовательно с кодами
+0000003E:   F029        BREQ      PC+0x06    // И переход на обработку если совпало
+0000003F:   3381        CPI       R24,0x31     // Аналогично и далее
+00000040:   F421        BRNE      PC+0x05

И по очереди сравниваем с кодами 0х30 и 0x31 в случае совпадения или не совпадения осуществляем переход либо на default либо на нужный case: которые будут ниже по тексту:

1
2
3
4
5
6
7
 	case '1': LED_PORT = 1<<LED2;	break;
+00000041:   E082        LDI       R24,0x02       Load immediate
+00000042:   BB88        OUT       0x18,R24       Out to I/O location
+00000043:   C001        RJMP      PC+0x0002      Relative jump
17:       	case '0': LED_PORT = 0<<LED2;	break;
+00000044:   BA18        OUT       0x18,R1        Out to I/O location
20:       }

Прерывание завершается массовым доставанием из стека данных которые мы туда сунули и командой RETI
Данные достаются в обратно порядке!

1
2
3
4
5
6
5:   918F        POP       R24            Pop register from stack
+00000046:   900F        POP       R0             Pop register from stack
+00000047:   BE0F        OUT       0x3F,R0        Out to I/O location
+00000048:   900F        POP       R0             Pop register from stack
+00000049:   901F        POP       R1             Pop register from stack
+0000004A:   9518        RETI

Работает! Задание вроде мы выполнили. Но …

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

Оставайтесь на линии :)

Текущее состояние программы в виде проекта для AVRStudio.

95 thoughts on “AVR. Учебный Курс. Программирование на Си. Часть 3”

  1. все правильно, только при возникновении прерывания генерируется не JMP а CALL. иначе не смогли бы вернуться.
    такой вопрос: почему для объявления обработчика используется ISR а не SIGNAL?

  2. «Вызов прерывания может быть когда угодно и где угодно, между любыми двумя командами (хотя хрена там, между CLI CLI его не будет :) »

    навероное всеже между CLI SEI

    1. О, вот оно. Точняк, помню была эта опечатка еще в прошлый раз, а сейчас раза три перечитал — не нашел. :) Fixed :)

  3. »
    И не забыв проинициализировать его порт на вход! В итоге, стало выглядеть так:
    LED_DDR = 1<<LED1|1<<LED2;
    »
    Ди, а не на выход ли O_o?

    1. Разве что когда она будет полностью написана и отлажена, выловлены все ошибки и опечатки. Тогда может быть да.

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

            1. Ну я привык к программированию под ПК, а там хидеры для настройки юзаются не так часто, а если и юзаются — то настраивается он через какой-нибудь configure и к этим настройкам привязана скомпилированная библиотека. В этих всех случаях хедер к себе тащить не требуется.

  4. Спасибо за уроки !
    Я использую CodeVisionAVR 2.04.4a , но за другими тоже слежу .
    Появилось QTouch Library (170 MB, revision 3.1, updated 11/09) после
    AVR Studio 4.17 (build 666) (112 MB, updated 7/09) .
    Могли бы вкратце оценить этот продукт ?

        1. Я был на атмеловском семинаре по поводу QTouch. Решение вполне юзабельное, на уровне некоторых конкурентов и даже повыше кое-кого (из конкурентов изучал еще SiLabs). Требует осторожной разводки платы и некоторого курения мануалов, но в плюсе — почти бесплатный touch interface с кучей

          1. … (гррр) вариантов, включая круговые движки, слайдеры, и конечно, просто кнопки. Доступно в качестве объектной библиотеки для включения в проект на меги начиная от 8-й, а так же в виде отдельных чипов.

  5. ошибка: Обработчик прерывания погасит второй сетодиод. Надо ставить и снимать бит.

    Кстати, вопрос есть:
    Мне нужно сервами управлять. В обработчике таймера идет что-то вроде

    outb(_SFR_IO8(ServoChannels[ServoChannel].port), inb(_SFR_IO8(ServoChannels[ServoChannel].port)) & ~(ServoChannels[ServoChannel].pin));

    ServoChannel++;

    outb(_SFR_IO8(ServoChannels[ServoChannel].port), inb(_SFR_IO8(ServoChannels[ServoChannel].port)) | (ServoChannels[ServoChannel].pin));

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

    Такая постановка и снятие бита выполняется очень долго, порядка 20 тактов на постановку бита и столько же на снятие. Жаба душит.
    На этапе компиляции известно, чем будут заполнены ServoChannels[].port и ServoChannels[].pin

    Пробовал:
    1. уйти от записи ServoChannels к отдельным массивам портов и пинов.
    2. уйти от _SFR_IO8 (этот макрос берет значение, добавляет к нему константу и приводит к указателю).
    3. завести указатель на ServoChannels[ServoChannel] и обращаться через него.
    Итог — все эти попытки не приводят практически ни к чему.

    Это все при том, что аппаратная постановка бита SBI/CBI — 2 такта.
    Но для того, чтобы воспользоваться SBI/CBI придется писать
    switch и в зависимости от номера сервы, менять вызывать соответствующую постановку или снятие бита. Тоже странно. Наверное, можно сделать относительный переход на ServoChannel*6, в каждом из вариантов будет статический
    SBI portN,pinN
    rjpm на конец обработки.

    Как такое делают красиво?

    1. А почему бы тебе через XOR тупо не инвертить биты серв? ЗАчем вычислять что надо сделать поставить или снять?

      Т.е. на входе у тебя есть битмаска порта. Где 0 это не трогать бит, 1 — инвертировать его.
      Так что ты заходишь в прерывание, тут же быстро вычисляешь битмаску и инвертишь по ней весь порт сразу.

      1. Инвертировать бит или установить/сбросить — считай, что одно и тоже время.
        Хочется иметь достаточно универсальный модуль для серв, который позволит работать с сервами, повешенными на разные порты контроллера пины.
        Если бы хотя бы пор был один и тот же, постановка и снятие бытов занимало бы не 20 тактов, в а пределах 10.

        Кстати, попробовал
        switch (ServoChannel)
        {
        case 0:cbi(PORTD,PIN0);break;

        case 6:cbi(PORTD,PIN6);break;
        }
        Реально быстрее работает, чем
        outb(_SFR_IO8(ServoChannels[ServoChannel].port), inb(_SFR_IO8(ServoChannels[ServoChannel].port))^(ServoChannels[ServoChannel].pin));

        1. Инверсия всего байта по XOR это три такта.

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

          1. В результате сделал криво (потом заменю на константы, есесно, чтобы можно было назначить разные порты и пины в нормальном виде)
            switch (ServoChannel)
            {
            case 0:PORTD|=_BV(PIN0);break;
            case 1:PORTD&= ~(_BV(PIN0));PORTD|=_BV(PIN1);break;
            case 2:PORTD&= ~(_BV(PIN1));PORTD|=_BV(PIN2);break;

            В среднем работает примерно на 30 тактов быстрее, чем с заданием или инверсией битов.

            А XOR и OR одинаково ж выполняются, нет?

      1. гм.
        Ну сходу — в main тоже самое с заданием битов — main и прервывание «тянут одеяло светодиодов на себя». Больше ничего глазами, не запуская проект, не вижу. Но я только логику проверяю, в регистры не лезу, могу пропустить еще два десятка ошибок :-).

  6. Кстати, попробовал воспользоваться твоим бутлоадером. Выяснилась интересная штука:
    Если компилять под atmeg16, то умещается в 512 слов, если компилять под atmegа168, то не умещается и большим запасом. Размер под atmega16 950 байт, под 168 — 1222.
    Есесно под 168-ю пришлось несколько изменить файлы, добавить корректный mega168.h, добавить штуки четыре define’а. Но в результате — меняешь проц на 16-ю мегу — 950 байт. Меняешь на 168 — 1222.

    Почему такая разница — не разбирался. Вроде процессоры очень похожи.

    1. Я тоже с этим столкнулся. Когда писал бут под 168 — тоже сильно не влезло :/

      Я думаю тут дело в том, что у 168 куда больше регистров периферии чем у меги16 и поэтому они по большей части все memory mapped (а это все уарты и прочее), а значит доступ к ним не в один OUT, а через Load-Store, что дольше на две три команды (воообщет на асме один в один, но черт знает как там себя оптимизатор ведет, может затупил и пошел через индексы все загонять)

    2. Кстати, а через что ты шил 168ю?

      Какие биты настроил чтобы его AVRProg понял. Я сигнатуру бутлоадера на 168 не нашел, начал подбирать подошла от 169го, а ты как выкрутился?

      1. А по мне пофиг, как его поймет AVRProg :-) Главное — чтобы были одинаковыми размеры flash и eeprom. Так что тупо оставил константы как у 16-й меги.

        #define DEVTYPE_ISP 0x74
        #define DEVTYPE_BOOT 0x75

        #define SIG_BYTE1 0x1E
        #define SIG_BYTE2 0x94
        #define SIG_BYTE3 0x03

        #define UART_BAUD_HIGH UBRR0H
        #define UART_BAUD_LOW UBRR0L
        #define UART_STATUS UCSR0A
        #define UART_TXREADY UDRE0
        #define UART_RXREADY RXC0
        #define UART_DOUBLE U2X
        #define UART_CTRL UCSR0B
        #define UART_CTRL_DATA ((1<<TXEN0) | (1<<RXEN0))
        #define UART_CTRL2 UCSR0C
        #define UART_CTRL2_DATA ((1<<UCSZ01) | (1<<UCSZ00))
        #define UART_DATA UDR0

        PS.В файлах mega16.h и mega169.h SIG_BYTE1..SIG_BYTE3 идут в противоположных последовательностях, однако значения у них схожие! Так что я взял именно mega16.h, как более проверенный.
        PPS: У avrprog вообще не заявлена совместимость с 168-м. По крайней мере я ее не обнаружил

        1. А у меня не заработало. :/ т.е. при попытке прошить на сигнатуре меги16 он дал мне ошибку чтения. Хотя бутлоадер определил. Сменил сигнатуру на мега169 — заработало.

  7. А я любитель в прерываниях заполнять массивы(входные выходные буфферы) или расставлять глобальные для программы флаги :) и совсем не любитель чтото делать в прерывании

    1. Ну это смотря, что надо делать — если действие укладывается тактов в 30-40, то можно его и в прерывании выполнить. Кроме того, бывают случаи, когда основная программа занята чем-то длительным и проще выполнить обработку чего-то в прерывании, чем отвлекать основной цикл на периодический просмотр флагов.
      А так, делаешь пару глобальных регистров флагов (типа volatile register uint8_t G_scheduler_reg asm(«r3»);) и в главном цикле на их основе выполняешь те, или иные действия.

  8. а тут можно оперировать типами unsigned long?
    что-нибудь типа:

    typedef unsigned long ulong;
    typedef unsigned char byte;

    ulong L = strlen(«someString»);
    byte *a = (byte*)malloc(L+1);
    memset(a, 0x00, L+1);

          1. вопрос исчерпан, подключил и string.h и stdlib.h, интересно как будет работать malloc, просто выделит память из ОЗУ?

            1. Просто выделит блок и вернет указатель. При этом 2 байта перед этим указателем — это заголовок, в котором хранится размер блока.
              Если я правильно понял, то:

              unsigned char *a = (unsigned char*)malloc(1000); // Выделение памяти
              unsigned short int L = *(unsigned short int*)(a-2) // L = 1000

    1. Можно, но лично я считаю, что удобнее оперировать типами в стиле uint32_t — точно знаешь, что именно получишь. =)

      [offtopic]
      Кстати, к автору статьи — небольшая придирка:
      >> Оформляется прерывание в WinAVR как:
      правильнее было бы написать не «в WinAVR», а «в avr-libc», потому что, WinAvr — это не компилятор, а лишь скомпиленный под винду и собранный в одно место гнутый тулчейн для работы с AVR.
      [/offtopic]

      1. Так то оно так, но везде куда чаще встречается название WINAVR так что пусть остается кае есть, чтобы не разводить путаницу.

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

          Ну и ещё из серии придирок:
          >> Так что берем и добавляем в код, где нибудь ДО процедуры main эту бодягу.
          Лично я прерывания всегда объявляю после main и всех прочих функций. Они же нигде не вызываются напрямую, следовательно их можно объявлять где угодно.

          1. > если, скажем, пока мы обрабатываем буковку из усарта, раза три щёлкнет таймер оверфлоу, то по выходу из прерывания усарта прерывание таймера обработается только один раз.

            +1

            > Лично я прерывания всегда объявляю после main и всех прочих функций. Они же нигде не вызываются напрямую, следовательно их можно объявлять где угодно.

            Ну раз пошла такая пьянка… Я обычно прерывания и прочий код, связанный с периферией, складываю в отдельные файлы. main() лежит в main.c, в котором больше нет ничего. Для каждого периферийного устройства — свой файл, у каждого (!) файла — свой .h, в котором лежат все макросы и нечасто меняемые константы. Если есть константы, которые надо подкручивать, выносим их в _conf.h, и все _conf складываем в отдельную папку.

            Вообще очень полезно посмотреть код к атмеловским AppNotes — в большинстве случаев структура проектов там сделана достаточно логично.

              1. А откуда там оверхед то? Какая разница где сорцы лежат?

                Я тоже обычно раскидываю все по куче файлов. У меня main это только то что аппаратно независимое. Все остальное стараюсь либо в дефайны сделать таким либо вынести в HAL

          2. во, справедливое дополнение.

            До или после майна это вопрос стилистики. В принципе без разницы, тоже сейчас добавлю.

        2. Поддерживаю gremlinable.livejournal.com.

          Я уже шестой год пользуюсь исключительно Linux. Windows нет ни дома, ни на работе. Поэтому WinAVR у меня просто нет. Мне сначала подумалось, что это какая-то особая сборка под винды. А оказывается, что это просто avr-libc.

          А под кучу файлов нужно выполнять несколько этапов компиляции, либо писать make. На самом деле не принципиально важно. И где main() определять тоже не важно.

  9. Привет всем. От чего может быть такое: при выходе из прерывания usart_rx, флаг I не восстанавливается. Cli не применяется. Отслеживал по авр студии. Причем, что интересно, при выходе из прерывания программа встает на то место, откуда зашла в прерывание, а потом сразу же прыгает на ресет.

      1. На асме. Стек работает. Точно.
        По выставлению галочки флага прерывания уходит по вектору, который в начале проги указан, туда куда надо.

      2. Блин. Стек работает, но неправильно как то(
        По уходу на прерывание, в стек грузится текущее значение счетчика команд, но перед ним откуда-то берется еще байт с нулями. В итоге он переходит на начало. Но откуда этот нулевой байт? Стек чистый, весь в ff, до того, как я начинаю переходить на этот обработчик. Дальше в обработчике стек работает как надо.

        З.Ы. все хотел спросить, да не знал куда написать: в АВР студии как-нибудь можно отображать нумерацию строк кода? Подозреваю, что нет(

          1. ldi acc, high(ramend)
            our sph, acc
            ldi acc, low(ramend)
            our low, acc

            В прокте закомментил уже все в основной части, после инициализации. Остался только джам на самого себя. При этом, когда вызываю прерывание, при возврате он упорно не хочет возвращаться на место.
            В дне стека, при входе в прерывание появляется 00 11. И SPL сменяется с 5F на 5D. Откуда этот ноль? Должен же появиться только адрес выхода из прерывания.
            Самое интересное, что открыл пару проектов, где все это работает. (Похожая программа, но на ранней стадии). Там тоже есть этот ноль и, при входе в прерывание, тоже 5D в SPL, но выходит он как нормальный человек на прерванное место, а не на ресет.

            1. так,

              1. тип мк не ошибся с выбором?
              3. При инициализации стека туда точно рамэнд записывается?
              2. как выглядит обработчик прерывания (вход и выход) и вектор.

              1. Уф.. Спасибо за помощь. Решилось))
                Дело такое.
                В обработчике уарта по чтению, как в книге у ревича запретил вначале и разрешил в конце прерывания по завершению чтения УДР (выходит самого себя) и прерывание по очистке УДР.
                Не знаю почему не понравилось это дело контроллеру, но получалось то, что описывал выше. Убрал запрет и разрешение этих двух прерываний и все заработало как надо. Трассируя прогу, никакой разницы не заметил. Что до изменений, что после.
                Зря на ноль в стеке грешил, выходит. Хотя, откуда он брался? Даже если и SPH записывался бы, то там должно быть 02 (мега8535 — SP-025F).
                И все-таки, неужели запрещение прерывания в уже выполняемом нём, может остановить его? И зачем тогда Ревич в примере так описывает?
                Про нумерацию кода в АВР студии так и не сказали, кстати. Есть она там?

                1. По идее ничего страшного бы не случилось. МОжет это просто глюк студии???

                  А вот чего РЕвич такое отмачивает я хз. Он не знает что при входе в обработчик прерывания запрещаются автоматом??? О_о

                  1. Спецом про это у вас на сайте перечитал + в Ефстифееве. Наверно я Ревича не так понял или хз.
                    Выходит что глюк студии.+ не она хочет работать с call 8535, только с rcall, в то время как avrasm32 (вроде бы так называется)все ок компилит.

  10. Доброго времени суток! Столкнулся с проблемой в связке tiny2313/codvision/proteus. Моделирую простейшее устройство — на пин PB0 (он же PCINT0, режим — in, Pull-Up) подключена кнопка, которая при нажатии просаживает его на землю.

    В программе настроено прерывание по изменению уровня PCINT0. В нем делаю простейшую операцию: изменяю состояние другой лапы на которой висит светодиод по состоянию лапы с кнопкой (соот-но светодиод должен вкл/выкл). Прерывание доходит, но вот состояние пина, подключенного к кнопке всегда 1. Из-за чего может такое наблюдаться?

  11. Ох… А AVR Studio не поддерживает симуляцию прерывания АЦП (АЦП запускаю по прерыванию таймера при совпадении). Даже если флаг сам ручками ставишь, то все равно на прерывание отладка не уходит…

  12. в avrStudio 5 чтобы прерывания работали надо #include
    я вчера 2 часа времени, из-за этого впустую убил, ибо интернета не было…

  13. Добрый день , уважаемый DI HALT, у меня возникла проблема при возникновении прерывания программа переходит в начало программы, а не на вектор прерывания
    Вот код:
    #include
    #include
    #include
    #define Freq 4000000

    INTERRUPT(SIG_OVERFOLW1)
    {
    TCNT1 = 65536 — (Freq/ 1024);
    PORTB ^= 0x01;
    }

    int main(void)
    {
    DDRB = 0x01;
    TCCR1A = 0;
    TCCR1B =5;
    TCNT1 = 65536 — (Freq / 1024);
    TIFR = 0;
    TIMSK = 0x04;
    GIMSK = 0;
    sei();
    while(1);
    }

    Контроллер ATmega8

    1. А вы точно уверены, что оно ТАК пишется? Вообще имена адресов векторов лучше смотреть не в книгах, а в h файлах проекта. Верней.

    1. в файле iom8.h вектора прописаны так:

      /* 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_UART_RECV _VECTOR(11)

      /* USART Data Register Empty */
      #define USART_UDRE_vect _VECTOR(12)
      #define SIG_UART_DATA _VECTOR(12)

      /* USART, Tx Complete */
      #define USART_TXC_vect _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)

      /* Store Program Memory Ready */
      #define SPM_RDY_vect _VECTOR(18)
      #define SIG_SPM_READY _VECTOR(18)

      И лучше оттуда копипастить напрямую.

    1. Ну там же можно биты и флаги выставлять. Попробуй сначачала бит выставить на ноге вручную. Поставь прозивольно паузу и потом ткни в бит на выводе. Если он нормально отрабоает то вот. Но студия может глючить с этим. Тогда можно вручную флаг нужного прерывания ткнуть.

      1. А какой бит для int1 срабатывания надо выставить ? Есть EIFR, в котором ставится бит, когда происходит Int1, но ставлю прогу на паузу, ставлю туда бит, а в int1 не попадаю.

  14. Привет, выручай. Случайно вшил в attiny прошивку, в которой была инструкция отключения PRR = (1 << PRSPI) как я понял, SPI интерфейса. У меня usbasp программатор, я так понял, я перевел мк в режим debugwire, как его теперь назад вернуть, а то теперь по SPI не видит.

    1. Если фуз биты не менял, то проблем быть не должно вообще. После сброса то все работает. А если ты спи в фузах прибил, тогда нужен другой программатор.

      1. Похоже сгорел порт MISO, проверил на осциллографе, именно по этой линии ничего не показывается в момент прошивки. Это вроде порт, по которому avrdude читает записанный бит, можно ли без этой проверки просто писать туда, не проверяя ?

          1. Пробую так
            avrdude -c usbasp -P USB -p m328p -U flash:w:myfirm.bin:r -F

            avrdude: warning: cannot set sck period. please check for usbasp firmware update.
            avrdude: error: programm enable: target doesn’t answer. 1
            avrdude: initialization failed, rc=-1
            avrdude: AVR device initialized and ready to accept instructions
            avrdude: Device signature = 0x000000
            avrdude: Yikes! Invalid device signature.
            avrdude: Expected signature for ATMEGA328P is 1E 95 0F
            avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
            To disable this feature, specify the -D option.
            Он не может считать сигнатуру и после этого ничего не пытается туда писать похоже.
            Пробовал еще ключ -V Do not verify. но такой же вывод дает.

Добавить комментарий

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha