AVR. Учебный Курс. Работа на прерываниях

Одним из серьезных достоинств контроллеров AVR является дикое количество прерываний. Фактически, каждое периферийное устройство имеет по вектору, а то и не по одному. Так что на прерываних можно замутить кучу параллельных процессов. Работа на прерываниях является одним из способов сделать псевдо многозадачную среду.

 
Идеально для передачи данных и обработки длительных процессов.

 
Для примера покажу буфферизированный вывод данных по USART на прерываниях.

 
В прошлых примерах был такой код:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Отправка строки
void SendStr(char *string)
{
while (*string!='\0')
	{
	SendByte(*string);
	string++;
	}
}
 
// Отправка одного символа
void SendByte(char byte)
{
while(!(UCSRA & (1<<UDRE)));
UDR=byte;
}

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

 
Как быть?

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

 
Поэтому прерывания, пожалуй, будет единственным адекватным вариантов.

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

  • RXT — прием байта. С этим понятно, мы его уже использовали
  • TXC — завершение отправки
  • UDRE — опустошение приемного буфера

 
Байты TXC и UDRE обычно вызывают путаницу. Поясню разницу.

 
Дело в том, что регистр передачи данных UDR в AVR на самом деле куда хитрей чем кажется, он двухэтажный. На первом ярусе, собственно UDR, а ниже находится конвейер сдвигового регистра. Первый байт, попавший в пустой регистр UDR тут же проваливается на конвейер, а UDR снова опустошается. После чего конвейер неторопливо, в соответствии с битрейтом, выплевывает данные в линию, а потом снова зажевывает байт из UDR. Поэтому, фактически, в UDR за короткое время влезает сразу два байта — первый тут же проваливается, а второй ждет.

 
Так вот,

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

 
Да, можно слать данные и по флагу TXC, но тогда у нас будет лишняя пауза между двумя разными байтами — время на опустошение буфера. Некошерно.

 
Вот как это можно сделать корректней.

 
Вначале выводим данные в массив, либо берем его из флеша — не важно. Для простоты запихну массив в ОЗУ. Код возьму из прошлой статьи:

 

1
2
3
#define buffer_MAX 16	// Длина текстового буффера
char buffer[buffer_MAX] = "0123456789ABCDEF"; 	// А вот и он сам
u08 buffer_index=0;				// Текущий элемент буффера

 
Инициализация интерфейса выглядит стандартно:

1
2
3
4
5
6
7
8
//InitUSART
UBRRL = LO(bauddivider);
UBRRH = HI(bauddivider);
UCSRA = 0;
UCSRB = 1<<RXEN|1<<TXEN|0<<RXCIE|0<<TXCIE;
UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
 
sei(); 		// Разрешаем прерывания.

Обратите внимание, что прерывания UDRE мы не разрешаем. Это делается потом. Иначе сразу же, на старте, контроллер ускачет на это прерывание, т.к. при пуске UDR пуст и мы получим черти что.

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

1
2
3
buffer_index=0;		// Сбрасываем индекс
UDR = buffer[0];		// Отправляем первый байт
UCSRB|=(1<<UDRIE);	// Разрешаем прерывание UDRE

Дальше можно затупить или делать вообще что угодно:

1
2
3
4
while(1) 
{
NOP();
}

В течении нескольких тактов выскочит прерывание UDRE

 

Да, кстати, я один раз словил гадский баг который отловил только трассировкой ассемблерного листнига. У меня была такая последовательность:

1
2
3
UDR = X; 
UCSRB|=(1<<UDRIE);
buffer_index = 1;

 
И вот тут почему то первым байтом шел мусор. А дальше все нормально. Причем если менять уровень оптимизации, то баг то вылезал то нет. Причиной такого поведения являлось то, что я то надеялся на то, что прерывание UDRE выскочит гораздо поздней чем я присвою индексу буфера нужное значение (buffer_index = 1;) Но индюк тоже думал, а по факту я пихаю байт в UDR, он в тот момент естественно пуст и уже следующим тактом, на выполнении команды UCSRB|=(1<<UDRIE) данные проваливались в сдвиговый регистр, а UDR тотчас пустел и выставлял бит прерывания.
А дальше, в зависимости от оптимизации, этот бит успевал выставиться к моменту когда я выставлял верный номер индекса либо не успевал.
Проблема решилась перестановкой строк:
 

1
2
3
UDR = X;
buffer_index = 1; 
UCSRB|=(1<<UDRIE);

 
Отсюда правило:
Готовь все необходимые данные ПЕРЕД разрешением прерываний.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Прерывание по опустошению буффера УАПП
ISR (USART_UDRE_vect)		
{
buffer_index ++;			// Увеличиваем индекс
 
if(buffer_index == buffer_MAX)  	// Вывели весь буффер? 
	{
	UCSRB &=~(1<<UDRIE);	// Запрещаем прерывание по опустошению - передача закончена
	}
	else 
	{
	UDR = buffer[buffer_index];	// Берем данные из буффера. 
	}
}

 
Все, автоматика! Каждый раз когда UDRE пустеет прерывание срабатывает и бросает туда новых дров. Когда же буфер пустеет и индекс достигает максимума, то мы просто запрещаем прерывание UDRE и успокаиваемся.

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

 
Вот полный код:

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
#define F_CPU 8000000L
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avrlibdefs.h>
#include <avrlibtypes.h>
 
#define buffer_MAX 16				// Длина текстового буффера
char buffer[buffer_MAX] = "0123456789ABCDEF"; 	// А вот и он сам
u08 buffer_index=0;
 
//Прерывание по опустошению буффера УАПП
ISR (USART_UDRE_vect)		
{
buffer_index++;			// Увеличиваем индекс
 
if(buffer_index == buffer_MAX)  	// Вывели весь буффер? 
	{
	UCSRB &=~(1<<UDRIE);	// Запрещаем прерывание по опустошению - передача закончена
	}
	else 
	{
	UDR = buffer[buffer_index];	// Берем данные из буффера. 
	}
}
 
int main(void)
{
 
#define baudrate 9600L
#define bauddivider (F_CPU/(16*baudrate)-1)
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)
 
//Init UART
UBRRL = LO(bauddivider);
UBRRH = HI(bauddivider);
UCSRA = 0;
UCSRB = 1<<RXEN|1<<TXEN|0<<RXCIE|0<<TXCIE;
UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
 
//Это так, просто помигать. 
#define LED1 4
#define LED_PORT PORTD
#define LED_DDR DDRD
 
LED_DDR = 1<<LED1;
 
sei();
 
buffer_index=0;		// Сбрасываем индекс
UDR = buffer[0];		// Отправляем первый байт
UCSRB|=(1<<UDRIE);	// Разрешаем прерывание UDRE
 
while(1) 
	{
	LED_PORT=0<<LED1;
 	_delay_ms(1000);
	LED_PORT=1<<LED1;
 	_delay_ms(1000);
	}
}

 
Если грузануть его в Pinboard, предварительно подключив USART к FT232 и законнектиться терминалкой, то будет мигать наш LED4, а в терминалку от стрелятся байты ASCII кодов нашей строки. В это же время будет неторопливо тикать наш цикл с мигалкой.

 
Ну и, как всегда, пример кода в архиве.

111 thoughts on “AVR. Учебный Курс. Работа на прерываниях”

  1. buffer_index дожен быть помечен как volatile, если мы работам с ним из прерывания. Это очень важный момент. Если данная программа скорее всего будет нормально работать, то при работе с buffer_index вне прерывания при включенной оптимизации могут начаться косяки. ИМХО, стоит указать это в статье и объяснить, почему так. Нубы, которые только начинают изучать Си, часто с этим сталкиваются :)

      1. так в этом то и фишка! в коде программы оно (buffer_index) грузицца в регистр и там и остается (оптимизация), а в это время оно, в прерывании, меняется, а значение, которое в регистре, осталось без изменений. вот чтоб этого не случилось и имеет смысл (акадэмический) объявлять переменные, изменяемые в прерывательских процедурах как volatile.

        1. Нифига. Переменная глобальная. Так что любые модификации ее затрагивают именно ее изменения.

          Мы вошли в прерывание, изменили и вышли. Компилятор не может ее сныкать в регистр, т.к. ему четко сказано записать ее в память — глобальную переменную. Она тут никак не останется только на уровне регистров. Волатиль тут не нужна!

          А вот если бы была конструкция

          ISR {I++ }

          main { while(I<100){} }

          То в этом случае волатиль нужна обязательно, т.к. оптимизатор цикл разложит по регистрам, а то что I меняется в ISR ему невдомек.

          1. Ну в данном случае, может и покатит, но если вдруг захочется в main сделать что-то ещё с этой переменной могут вылезти неожиданные глюки =) Так что лучше сразу объявить её volatile и не бояться подземных грабель.
            Самое смешное, что GCC оптимизирует даже регистровые переменные — если интересно, я как-то писал про то, как наступил на эти грабли: http://gremlinable.livejournal.com/6808.html

            Да, забавно ещё заметить, что в IAR ключевое слово volatile обозначает вообще совершенно другое… =)

  2. И мне кажется более красивым способ, если сделать buffer_index типа char*.
    Тогда вначале мы присваиваем ему адрес буфера: buffer_index=buffer;
    А потом делаем так:
    ISR (USART_UDRE_vect)
    {
    if(*buffer_index) // Вывели весь буффер?
    {
    UDR = *buffer_index; // Берем данные из буффера.
    buffer_index++; // Увеличиваем индекс
    } else {
    UCSRB &=~(1<<UDRIE); // Запрещаем прерывание по опустошению — передача закончена
    }
    }

        1. Попутно расставляя другие. Тем более данные функции, ЕМНИП, существуют только в avr-libc и при переносе на IAR или CVAVR вылезут в полный рост.

          А все платформозависимые функции выносятся в HAL и там и остаются.

  3. а вот очень простая реализация буфера FIFO на асме. bufferstart и bufferend — адрес начала и конца буфера в СРАМ, buffervalue — регистр для обмена данными с буфером. буфер закольцован, когда он полон данные перестают записываться и выставляется флаг bufferfull, а когда пуст перестают считываться и выставляется bufferempty.

    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
    
    ;
    BUFFER_WRITE:
     
    	sbrc flag,fbufferfull  	;если не установлен флаг "буфер полон" пропускаем
    	rjmp BUFFER_WRITE_EXIT	;иначе на выход
     
    	st X+,buffervalue	;заносим значение в буфер, увеличиваем указатель записи
    	cbr flag,(1<<fbufferempty)	;сбрасываем флаг "буфер пуст"
    	cpi XL,bufferend		;проверяем указатель записи на достижение максимального адреса
    	brne BUFFER_WRITE_01	;если не достиг пропускаем
    	ldi XL,bufferstart		;иначе перескакиваем на минимальный адрес
    BUFFER_WRITE_01:
    	cp XL,YL		;проверяем, указатель записи догнал указатель чтения?
    	brne BUFFER_WRITE_EXIT	;если не догнал пропускаем
    	sbr flag,(1<<fbufferfull)	;устанавливаем флаг "буфер полон"
     
    BUFFER_WRITE_EXIT:
    	ret
     
     
    BUFFER_READ:
     
    	sbrc flag,fbufferempty 	;если не установлен флаг "буфер пуст" пропускаем
    	rjmp BUFFER_READ_EXIT	;иначе на выход
     
    	ld buffervalue,Y+	;читаем значение из буфера, увеличиваем указатель чтения
    	cbr flag,(1<<fbufferfull)	;сбрасываем флаг "буфер полон"
    	cpi YL,bufferend		;проверяем указатель чтения на достижение максимального адреса
    	brne BUFFER_READ_01	;если не достиг пропускаем
    	ldi YL,bufferstart		;иначе перескакиваем на минимальный адрес
    BUFFER_READ_01:
    	cp YL,XL		;проверяем, указатель чтения догнал указатель записи?
    	brne BUFFER_READ_EXIT	;если не догнал пропускаем
    	sbr flag,(1<<fbufferempty)	;устанавливаем флаг "буфер пуст"
     
    BUFFER_READ_EXIT:
    	ret
    1. таким образом, при передаче пакета данных можно выделить буфер в памяти и в основном цикле после проверки флага «буфер полон» заполнять его необходимыми данными для отправки и разрешать прерывание UDR, а в прерывании по UDR считывать значение из буфера и если он опустошился — запрещать прерывание UDR. это позволит отправить пакет данных без пауз.

  4. В серьезных системах возникает дополнительная проблема — сложность полного обработчика прерывания. Например, по прерыванию нужно выкачать из устройства пакет данных, или сделать с ним еще что-то, требующее сложного ввода-вывода. Размещение такого кода непосредственно в обработчике прерывания будет мешать принятию и обработке прерываний от других устройств. Поэтому, в ядре ОС есть специальная очередь задач обработки прерываний (в одной из известных ОС такие задачи называются IRP — Interrupt Request Packet). Приоритет исполнения задач в этой очереди ниже приоритета собственно обработчиков прерываний, но выше приоритета обычных пользовательских процессов. Собственно обработчик вектора прерывания в драйвере устройства очень прост с точки зрения нагрузки на процессор. Он уведомляет устройство и контроллер прерываний, что прерывание принято к сведению, и добавляет задачу обработки прерывания (IRP) в эту очередь. IRP — это довольно простая структура данных, содержащая базовые данные о том, какому драйверу и устройству оно соответствует, и указатели на функции в драйвере, которые нужно исполнить.

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

    1. Гораздо удобнее система многоуровневых приоритетных прерываний, реализованная, например, в Интеловском контроллере прерываний I8059A еще в конце 70х годов. 8 векторов и 8 уровней приоритетов с возможностью наращивания каскадированием контроллеров, фиксированные или циклически меняющиеся приоритеты (например, уже обработанному прерыванию можно было присваивать самый низкий приоритет, или сдвигать приоритеты по кольцу), удобные маски, 4х или 8ми байтовый шаг таблицы векторов, и много еще всякого… С ним я не имел проблем с обработкой событий реального времени. Прерывание с более высоким приоритетом могло прервать обработку более низких, которая после будет опять продолжена, и само могло прерваться для обработки еще более высоких. Как я по ним скучаю…
      Все, что сейчас реализовано в наиболее распространенных микроконтроллерах — лишь слабое подобие той системы. А ведь это сильно упрощало программы реального времени и снимало массу проблем… Городить же программно еще отдельный диспетчер для формирования и обработки очереди прерываний — только еще больше запутывать и тормозить программу.
      Все равно эта конструкция никогда не заменит нормального аппаратно реализованного полноценного многоуровневого приоритетного контроллера прерываний… Не понимаю, почему бы не встроить такую штуку в современные микроконтроллеры. Схема его (по современным меркам) довольно проста, и сильно кристалл не усложнит.

  5. Это все работает, если:

    sei();
    buffer_index=0; // Сбрасываем индекс
    UDR = buffer[0]; // Отправляем первый байт
    UCSRB|=(1<<UDRIE); // Разрешаем прерывание UDRE

    находится до цикла WHILE

    А если помести его за циклом — не работает. Как быть?

      1. Ааа, вот оно что….
        Да, смотрел код асм — и конечно все верно — он прыгает вечно от строчки к строчке, зацикливается (сомтрел на ассме, просто написал тут на си):
        98: sei(); // Разрешаем прерывания
        99: UDR = buffer[0]; // Отправляем первый байт
        100: UCSRB|=(1<<UDRIE); // Разрешаем прерывание UDRE
        100: UCSRB|=(1<<UDRIE); // Разрешаем прерывание UDRE

        Но почему тогда выполняется передача первого числа — а только потом уже входит в цикл… Ведь и передача первого числа в цикле уже стоит…

          1. Да, наверное сразу надо было начинать с этого, чтобы не вводить в заблуждение.

            Мне нужно проверить, замкнут ли контакт на землю, и если замкнут — отсылать сообщение по UART. Как только замыкание на землю исчезает — передача прекращается.

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

            Очень понравился этот урок — и начал делать на его основе.
            Для начала (чтобы не наделать ошибок) решил бесконечно отсылать по UART строку, ну только что добавил в обработчик прерывание проверку:

            buffer_index++; // Увеличиваем индекс
            if(buffer[buffer_index] == 0x00)
            {
            UCSRB &=~(1<<UDRIE); // UCSRB &=~(1<<UDRIE)- Установка бита: запрещаем прерывание по опустошению — передача закончена
            }

            Я как бы конечно буду знать заранее, что я отправляю, т.е. количество символов — но все же я одновременно и изучаю еще си (а с подачки этого сайта одновременно и асм :) и решил сделать такую проверку — буфер у нас 16, а мне нужно передать например 3 символа — после 3 символов начнется передача символа NULL (0x00) — и если такое замечаем — прекращаем передачу.
            Все работало.

            Но как я понял из кучи литературы (видимо неправильно понял) — основная программа должна выполняться в цикле, и я просто перенес строки:

            sei(); // Разрешаем прерывания
            UDR = buffer[0]; // Отправляем первый байт
            UCSRB|=(1<<UDRIE); // Разрешаем прерывание UDRE

            в цикл while (1)
            Мигание диодами я убрал, не нужно оно мне пока что тут, в AVRStudio и так отлично видно, что получается.

            И вот в симуляции получается так: доходит до цикла while, отправляет в UDR первый байт, т.е. buffer[0], перескакивает на обработчик, отрабатывает его 1 раз (!!!) и позвращается к циклу, т.е. фактически передается только первое число из строки.
            Ну а в цикле от уже крутится бесконечно — прерывание не обрабатывается… А в цикле у нас передача первого символа — и он постоянно его пихает в UDR, но на обработчик прерывания не переходит…

            Интуитивно я догадываюсь, создавать функцию отправки до бесконечного цикла, и вызывать ее в цикле.. Так?

            1. ты не путай теплое с мягким.

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

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

  6. Доброго времени суток! возникла такая задача: построить клавиатуру по распределенной схеме. 1ый МК (atmega16) совершает опрос клавиатуры (сканирование) и передает код нажатой кнопки по uart. планируется передача 1-3 байт за сеанс передачи. (один байт описывает состояние ctrl, alt, shift, второй содержит номер нажатой клавиши, третий — номер второй нажатой клавиши, если нажато 2 одновременно ) Постоянно пересылать состояние всех клавиш счел нецелесообразным. Второй МК (pic18f2550) принимает данные по уарт и передает в компьютер по usb, определяясь как стандартное hid устройство. Реализация usb клавиатуры подошла от фреймворка microchip, с этим нет вопросов — работает стабильно. На стороне и принимающего, и передающего МК будет создан циклический буфер.

    ПОСТАНОВКА ВОПРОСА: как лучше реализовать обмен по UART — с прерываниями или без ( чтобы не было конфликта прерываний usb и uart ) Больше всего интересует со стороны принимающего мк. Как это вижу я. в цикле ждем завершения очередного приема, проверяя флаг. По завершении приема записываем полученное значение в регистр usb. usb сам с нужной ему частотой инициирует прерывание и передачу данных в ПК. Какие рекомендации и советы можете дать?

        1. При сдвиге влево, справа автоматом вылезают нули. так что там нет смысла отрезать старший байт. Он и так нулевой.

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

  7. Добрый день, может кто объяснит
    ситуация такая, купил новую ATmega32, по идее F_CPU у нее с завода 1000000, в настройках USART пишу так
    [code]
    #define F_CPU 1000000L
    #define baudrate 9600L
    #define bauddivider (F_CPU/(16*baudrate)-1)
    #define HI(x) ((x)>>8)
    #define LO(x) ((x)& 0xFF)
    //Init UART
    UBRRL = LO(bauddivider);
    UBRRH = HI(bauddivider);
    UCSRA = 0;
    UCSRB = 1<<RXEN|1<<TXEN|0<<RXCIE|0<<TXCIE;
    UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
    [/code]
    на скорости 9600 выдает кракозябры
    меняю F_CPU на 8000000
    [code]
    #define F_CPU 8000000L
    [/code]
    больше ничего не меняю, начинает работать правильно, но на скорости 1200, в чем может быть проблемма?

  8. Здравствуйте. Помогите кто может! Делаю стыковку по UART двух Attiny2313. Отладку произвожу по отдельности на макетке. При отладке всё пашет идеально, но как втыкаю МК в плату, UART начинает ловить кучу мусора, причём безостановочно. Как это остановить?
    Вот такая плата:
    http://fotoifolder.ru/view_foto/i9jcbejhb9fx/
    Хотя это вопрос скорее по теме разводки платы или схемотехнике, но мне кажется что здесь этот вопрос тоже уместен. Помогите, пожалуйста.

  9. DiHALT, слушай, я написал простейшую программу в СodeVision — по нажатии на кнопку int0 выходит в прерывание и диодиком один раз мигает.

    Диод вместо одного раза мигает два (и вообще функция прерывания 2 раза выполняется). Это программный глюк или просто дребезг контактов (происходят два прерывания и в очередь выстраиваются) ?

      1. Огромное спасибо, успокоил! Твой сайт — прямо как спасательный круг :)))

        Получается, что прерывание срабатывает и его флаг устанавливается/сбрасывается быстрее, чем дребезг второй раз прерывание запускает и флаг заново устанавливает (флаг кажется сразу после запуска прерывания сбрасывается)?

        1. Флаг сбрасывается при переходе на вектор. По крайней мере для INT так. Для других прерываний по своему. Соответственно тебе, чтобы это пофиксить надо в прерывании отключать прерывание по INT, запускать какую либо фоновую задержку (например через службу таймера или аппаратным таймером) которая бы через 10-20мс вернула все взад. Тогда и событие поймаешь и дребезг подавишь.

        2. А вообще ловить кнопки через INT моветон. Гемора больше чем пользы. Кнопка штука не сильно срочная и ее можно в обычном циклическом опросе раз в 10 в секунду проверять просто.

  10. подскажите пожалуйста!
    уже совсем сил нету! запускаю на меге 16-й ЮСАРТ, в протеусе все норм работает! а в реале не конает! через терминал принимаю данные, а там много ошибок! немного подстраиваю скорость, чтот лучше, но всетаки 100% на 9600 передачи не наблюдаю! в чем может быть причина???
    передаю число от 0 до 255, инкремент и выполнение передачи по прерыванию!
    заранее спасибо!

      1. да! частота та! смотрел осциллом! но она немного плавает!(это сильно может повлиять?)
        стоит внешний кварц, на 14, 7… МГц, коэфф деления для скорости выбран с таблицы, с нулевой погрешностью! питание достаточно чистое! dsPIC, в таком режиме передачи, работает на ура!

        1. Тогда хз. Там еще есть бит U2X вот проверь его состояние. Он удваивает частоту уарта. Еще есть всякие предделители частоты. Частоту то на чем смотрел? На кварце? Ядро может работать совсем на другой частоте если есть преддилетль или настроено не на кварц.

  11. Здравствуйте. Я новичок. Поэтому у меня такой вопрос. Как я понял, у нас основной цикл программы — это мигание диодом. А по прерыванию происходит отправка данных по UART. То есть получается во время прерывания одновременно идет мигание светодиодом и отправка данных. Я не совсем понял как именно наступают прерывания. В статье написано «Прерывание по опустошению буффера УАПП» — то есть получается, что у нас одновременно со светодиодами происходит опустошение буфера, а вслед за ним происходит прерывание? Разъясните пожалуйста, а то я запутался :)

        1. Спасибо, во всем разобрался. Получается, что светодиоды тикают, пока не наступает прерывание. Как только оно наступило, то начинается обработка прерывания. Начинаем передавать данные.

  12. Подскажите пожалуйста. при вызове функции из обработчика прерываний что происходит с флагом i ? Т.е. нужно ли мне запрещать глобально прерывания перед её вызовом или нет?
    ISR(TWI_vect)
    {
    switch(flags.twi_state)
    {
    case RTC:
    cli(); //————>>НУЖНО ЛИ ЭТО??
    twi_read(RTC_ADDRESS,RTC_REGISTER,RTC_LENGTH);
    bcd_to_ascii();
    sei(); //————И ЭТО??
    break;

  13. char buffer[buffer_MAX] = "0123456789ABCDEF";
    
    //Прерывание по опустошению буффера УАПП
    ISR (USART_UDRE_vect)
    {
    	if(buffer_index == buffer_MAX) // Вывели весь буффер?
    	{
    		UCSRB &=~(1<<UDRIE); // Запрещаем прерывание по опустошению - передача закончена
    	}
    	else
    	{
    		UDR = buffer[buffer_index]; // Берем данные из буффера.
    	}
    	buffer_index++; // Увеличиваем индекс
    }
    
    
    main
    {
    	buffer_index=0; // Сбрасываем индекс
    	UCSRB|=(1<<UDRIE); // Разрешаем прерывание UDRE
    	while(1)
    	{
    		;
    	}
    }
    
    1. А это мысль. Можно и так. Подготовил данные, включил прерывание. Правда при этом данные пойдут не мгновенно, а только когда прерывание его обработает. Но зачастую это не критично.

  14. В этой программе я пытаюсь вывести данные (звук) из wavetable на вывод в ШИМ. Но проблема в том, что в переменную i почему-то записывается какой-то мусор. Что я делаю не так?
    Код для AVR Toolchain:

    1. У тебя здоровенная таблица, убедись, что она не продублирвоалась в RAM, а то может занять всю память и стек будет затирать ее, в том числе и i. Попробуй уменшить массив wavetable до десятка значений, чтобы наверняка. И погоняй.

  15. Доброго времени суток. После прочтения всей серии статей, у меня возникло больше вопросов чем ответов. Нет не из-за того что статьи написаны плохо, и не в коем случае не хотелось бы обидить автора. Просто мне не понятны некоторые вещи по сути из-за не хватки опыта в прогромировании мк.
    Если можно поделится списком литературы по тем вопроса что расматриваются в данной серии статей.(Суперцикл Суперцикл+прерывания Флаговый автомат Диспетчер Приоритетный диспетчер Кооперативная RTOS Вытесняющая RTOS) Буду очень признателен

    1. Черевато срывом указателя в никуда. А вот ASCIIZ там строка или ASCII я честно говоря как то и не помню. Вроде бы ASCIIZ и ноль нужно учитывать, но это проще в дебагере запустить и посмотреть что там создастся в памяти. Вообще дебагер универсальный ответ на большинство вопросов. Чем искать и спрашивать, проще один раз запустить и посмотреть самому.

  16. В вот у меня ничего Не выходит. Контроллер Мега169P. Не получается у меня разобраться в какой момент включать UDRIE если UDRE поднят. Ну не понятно!!! Уже неделю сижу.
    В статье: AVR. Учебный Курс. Работа на прерываниях
    Была описана работа с АРТом, но Я ничего не понял.
    Вот сижу сейчас с STK500 в обнимку и тихо «cry».
    Куда вам написать и где оставить свою версию программы, чтобы вы мне помогли?

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

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

    1. 1) Сколько ждете? Как ждете? В симуляторе? В симуляторе можно долго ждать, т.к. байт передаваться будет сотни тактов.
      2) Прерывание правильно задано? Вектора? Разрешены глобальные прерывания?

      Лично не консультирую, пишите в комментарии.

      1. Хорошо. Попробую объяснить на пальцах. Хочу я изучить, как же проще(по короче) и быстрее отправить байт.
        Это для начала, пока изучаю. Для общей картины, скажу… Что потом нужно будет мастерить пару буферов обмена… Первый для связи из Мк1 в комп. Второй — приемный, из компа в мк и третий из мк1 в мк2 по spi (фишка это радиомодуль NRF24L01). И даже может быть четвертый — приемный для связи мк2 с компом. Последний будет зашит в МК1 и перекидывать байтики с МК2 в комп.
        И еще что-то надо будет придумать с реакцией на команды. То есть, первый мк должен будет реагировать на последовательность принимаемых символов и принимая их, выполнить последовательность функций. Своего рода настоящий конечный автомат. Я еще не знаю, как это все исполнить, ибо давно это было(учился я лет 10 назад и многое уже потерял и забыл). Но это все в перспективе =)) Ну а пока я застопорился на UART. Написал вот такую программу. Для наглядности развернул все настройки, чтобы проверять потом было по быстрей.
        https://yadi.sk/d/2531mPB5cbsUu
        Написал все в аврстудии 6.2. Контроллер 169Р Еще я пока не умею разделять проект на *.h и *.c файлы. Поэтому все засунул туды срр.

        1. А не понимаю, я алгоритма работы передатчика, тем более, что вы применили секретно-ингридиентный прием кунг-фу — передачу по «поднятию» флага UDRIE. Сама метода меня заинтересовала, потому и пришел сюда =)
          Но я не ожидал, что можно этой метОдой погрузить мк в неопределенное состояние. Он просто зависает.

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

            1. Ну протеус не показатель. Его эмулятор довольно кривой и лучше проверять в железе. Разбирайтесь с тем что у вас происходит. Прерывание уарта срабатывает? А возврат нормально происходит? Сам код инициализации уарта выполняется? А засылки байта? А Частота какая? А уарт настроен правильно? Вот на все эти вопросы надо ответить в ходе отладки.

            1. Напишите пожалуйста пример в котором будет отсылаться число от 0 до 100 через uart на c++ с помощью цикла for. Попробую разложить его в ассемблер и понять…
              Еще у меня нет jtag отладчика. Я не могу по тактово исследовать команды. =( Видел как народ весело исследует процессы, но для меня jtag ice пока дороговат.
              Видимо зависает, потому что флаг UDRE и флаг UDRIE подняты, контроллер уходит в прерывание и там где-то зависает. Я не знаю за что зацепиться можно в такой ситуации. может я данное засунул неправильно и оно у меня имеет значение 00. Также я пока не понял, как нужно значение запихивать внутрь подпрограммы прерывания. На ассемблере, когда я вижу, что ячейка R24 заполняется другим значением при входе прерывание, меня это сбивает. Я не знаю что отправится в итоге

              1. C помощью цикла for это на тупом ожидании, тебе он не поможет.

                JTAG отладчик и не нужен. Достаточно светодоидика на линии. Еще может помочь AVR Simulator, но для этого надо выбросить нахер ублюдочную студию 6 и работать в рассово правильной AVR Studio 4.19.

                Данные значения не имеют. Флаги UDRE сигнализирует о том, что регистр пуст и можно грузить. А UDRIE просто говорит о том, что прерывание по опустошению буфера разрешено.

                Зацепляться тут на самом деле много за что. Проверь входит ли программа в вектор прерывания. Как? Воткнув туда светодиод с тупой задержкой на for в 1 секунду, моргнет — значит вошло. Можешь еще вторым светодиодом отследить работу фоновой задачи. Надо лишь сделать так, чтобы в фоновой задаче светодиод мерцал и это было видно. Осциллограф есть? Если есть, то можно просто переключать вывод в 1 и 0 в главном цикле, это даст тебе понимание того как работает фон. Если осцила нет, то надо внести задержку такую ,чтобы видно было на глаз мерцание. Если перестанет мерцать — значит прерывание произошло (и должен загореться диод прерывания). А потом должно вернуться к мерцанию, т.е. возврат к фону. Е

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

                1. С мерцанием светодиода я настраивал таймер. Очень результативный метод. =) Но вот как-то я не догадался в прерывание по UDRIE всабачить туда Порт ^=();
                  Хорошо. Приду домой с работы, попробую войти в прерывание.
                  Знать бы, когда именно он туда уходит, именно тогда, когда флаг UDRE поднят? С таймером все понятно. Добижали до конца, если равно, то сброс с отработкой тела подпрограммы. А тут получается, что после отправки поднимается флаг и отработка тела? По логике все так, а может и немножко не так… Это мое понимание

                    1. Верно. Пишем туда байт и флаг UDRE опускается? Байт проглочен. Что дальше?

                    2. Так точно. А дальше ждем Как только он байт прожует, то поднимает снова UDRE и мы улетаем в обработчик прерывания. Там проверяем наши данные? Есть еще байт на отправку? Если да, то кладем его в UDR и выходим (вернемся сюда сами ,как удр опустеет и флаг снова поднимется), а если байтов на отправку больше нет, то выключаем прерывание сняв флаг UDRIE

                    3. «Там проверяем наши данные»….,? А как имeнно проверяем и на что?…
                      «Есть еще байт на отправку? Если да, то кладем его в UDR и выходим»
                      Получается двойной удар ногой? Вот этот прикол я и не понимаю =)

                    4. Как это на что? На то кончились у нас данные или еще есть что послать.

                      Какой двойной удар ногой? Данные так и передаются — кладутся в UDR и все. Дальше уарт делает все сам.

                    5. Какой двойной удар ногой? =)))
                      Вспомни Жан-Клод Ван Дамм. Его фишка такая =)
                      Сначала суем данные в буфер, они отправляются и поднимается флаг по опустошению. Потом по прерыванию суем туда еще байт и он отправляется… Ну а дальше вообще кунгфу =)

                    6. Я фильм помню такой, правда я его не смотрел. Двойной удар! Двойной Ван Дамм! У него там еще брат близнец был.

                      Да, все верно. Данные лежат в памяти в неком массиве где мы их накопили перед отправкой. Мы в фоновой программе берем первый байт массива, кладем в буфер уарта, включаем прерывание по опустошению. А дальше все остальное сделает код в прерывании, автоматически. Фоновая же программа идет своим чередом.

                    7. Ну так это, 57 строка. А зачем ты прерывания то глобально запретил? ;)

                    8. Дык яж записал в UDR0 байт и чуть позже, в функции разрешил прерывания. Ведь, я думаю, что если допишу сюда таймер, то он начнет сбивать запись в UDR0. Где я не прав? Я просто боюсь, сбить этот момент. Потом после функции эмка уходит в прерывание…

                    9. нет. Это все не работает. Даже убрав cli и вместо него разрешив прерывания, ничего не работает. Контроллер не отправляет данные. За-то он очень хорошо программируется. Попробуй проэмулируй сам. У меня вариантов больше нет

  18. Дело было не в бобине и не в бубне, а в скорости. Для atmega169Р и кварца 7372800 скорости в 9600 бод не существует, потому что он работает со скоростью в 19200. Теперь я могу экспериментировать дальше. Спасибо автору статьи. И может этот коммент кому сгодится.

  19. А у меня код из статьи, слегка переписанный, работает не так. Он постоянно пересылает буфер и не останавливается, как должен.
    В чём подвох?
    Пишу я для Arduino Mega, собираю GCC, заливаю XLoader’ом. А все изменения в коде сводились к тому, чтобы подставить циферки 0 к именам регистров и поменять F_CPU на 16000000L

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