воскресенье, 3 августа 2014 г.

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

Данный материал взят с http://easyelectronics.ru/avr-uchebnyj-kurs-rabota-na-preryvaniyax.html

Опубликовано  автором DI HALT

Распечатать
Одним из серьезных достоинств контроллеров 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 кодов нашей строки. В это же время будет неторопливо тикать наш цикл с мигалкой.

1 комментарий:

  1. Online Casino | Play and Win with Our Bonus - Freebies
    Baccarat, as the name implies, is a game that can be played at casinos for 1xbet beginners and those with an interest 제왕카지노 in a particular 바카라 game that has many ways

    ОтветитьУдалить