Микроконтроллеры AVR. UART. Использование прерываний.

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

Даже не знаю, какую бы придумать задачку..А давайте так — будем принимать данные по UART, а затем сразу же высылать их обратно. Если все будет правильно реализовано, то передаваемые и принимаемые данные должны полностью совпадать.

Сначала, пожалуй, приведу полный код программы, а потом все детально обсудим )

/*************************************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
 
 
 
/*************************************************************************************************/
#define AVR_USART_9600_VALUE						(103)
 
 
 
/*************************************************************************************************/
typedef enum {FALSE = 0, TRUE = !FALSE} bool;
int i = 0;
unsigned char testBuffer[8];
bool readyToExchange;
unsigned char numOfDataToSend;
unsigned char numOfDataToReceive;
unsigned char *sendDataPtr;
unsigned char *receivedDataPtr;
unsigned char numOfDataSended;
unsigned char numOfDataReceived;
 
 
 
/*************************************************************************************************/
void UART_SendData(uint8_t *pSendData, uint8_t nNumOfDataToSend)
{
    sendDataPtr = pSendData;
    numOfDataToSend = nNumOfDataToSend;
    numOfDataSended = 0;
    readyToExchange = FALSE;
 
    UCSR0B |= (1 << UDRIE0) | (1 << TXEN0);
}
 
 
 
/*************************************************************************************************/
void UART_ReceiveData(uint8_t* pReceivedData, uint8_t nNumOfDataToReceive)
{
    receivedDataPtr = pReceivedData;
    numOfDataToReceive = nNumOfDataToReceive;
    numOfDataReceived = 0;
    readyToExchange = FALSE;
 
    UCSR0B |= (1 << RXCIE0) | (1 << RXEN0);
}
 
 
 
/*************************************************************************************************/
void main(void)
{
    sei();
    UBRR0 = AVR_USART_9600_VALUE;
 
    while(1)
    {
    	UART_ReceiveData(testBuffer, 8);
    	while(!readyToExchange);
	for (i = 0; i < 500; i++);
	UART_SendData(testBuffer, 8);
	while(!readyToExchange);
    }
}
 
 
 
/*************************************************************************************************/
ISR(USART0_UDRE_vect)
{
    UDR0 = *sendDataPtr;
    sendDataPtr++;
    numOfDataSended++;	
    if (numOfDataSended == numOfDataToSend)
    {
	UCSR0B &= ~(1 << UDRIE0);
	readyToExchange = 1;
    }
}
 
 
 
/*************************************************************************************************/
ISR(USART0_RX_vect)
{	
    *receivedDataPtr = UDR0;
    receivedDataPtr++;	
    numOfDataReceived++;
    if (numOfDataReceived == numOfDataToReceive)
    {
    	UCSR0B &= ~((1 << RXCIE0) | (1 << RXEN0));
	readyToExchange = 1;
    }
}
 
 
 
/*************************************************************************************************/

Итак, что мы тут видим? 😉

Для начала подключаем все необходимые файлы. В данном случае их всего лишь два.

#include <avr/io.h>
#include <avr/interrupt.h>

Для настройки UART’а нам понадобится значение, которое необходимо записать в регистр UBRR0. Пусть скорость будет у нас 9600 кБ/с. Смотрим в даташите нужное значение и дефайном определяем переменную:

#define AVR_USART_9600_VALUE			(103)

Теперь нам необходимо объявить все переменные, которые нам понадобятся. А кроме того, определим тип данных bool:

typedef enum {FALSE = 0, TRUE = !FALSE} bool;
int i = 0;
unsigned char testBuffer[8];
bool readyToExchange;
unsigned char numOfDataToSend;
unsigned char numOfDataToReceive;
unsigned char *sendDataPtr;
unsigned char *receivedDataPtr;
unsigned char numOfDataSended;
unsigned char numOfDataReceived;

На этом приготовления можно считать законченными )

Для отправки данных мы будем использовать функцию UART_SendData():

void UART_SendData(uint8_t *pSendData, uint8_t nNumOfDataToSend)
{
    sendDataPtr = pSendData;
    numOfDataToSend = nNumOfDataToSend;
    numOfDataSended = 0;
    readyToExchange = FALSE;
 
    UCSR0B |= (1 << UDRIE0) | (1 << TXEN0);
}

В качестве аргументов эта функция принимает адрес массива с данными, которые необходимо передать и количество этих самых байт данных. Адрес массива сразу же копируем в переменную sendDataPtr, ее мы потом будем использовать в прерывании по опустошению регистра данных (UDR). Задаем начальные значения всех остальных переменных, из их названия интуитивно понятно, для чего они будут использоваться 😉

Флаг readyToExchange сигнализирует об окончании приема/передачи. То есть, передав все наши байты, мы выставляем этот флаг в единицу. И, конечно же, мы должны разрешить, собственно, передачу и необходимое прерывание:

UCSR0B |= (1 << UDRIE0) | (1 << TXEN0);

Пишем обработчик прерывания:

ISR(USART0_UDRE_vect)
{
    UDR0 = *sendDataPtr;
    sendDataPtr++;
    numOfDataSended++;	
    if (numOfDataSended == numOfDataToSend)
    {
    	UCSR0B &= ~(1 << UDRIE0);
    	readyToExchange = 1;
    }
}

Тут мы суем данные в регистр UDR0, откуда они вылетают во внешний мир. Естественно, инкрементируем указатель на данные, чтобы передавать последующие байты. Также инкрементируем переменную-счетчик для количества отправленных байт. Затем проверяем, равно ли количество отправленных байт, количеству байт, которые необходимо отправить. Если равно, то значит наша работа закончена, мы все отправили, можно прикрывать лавочку, то есть запрещать прерывание:

UCSR0B &= ~(1 << UDRIE0);

Вот так вот)

При приеме данных ситуация примерно такая же, программа построена похожим образом:

void UART_ReceiveData(uint8_t* pReceivedData, uint8_t nNumOfDataToReceive)
{
    receivedDataPtr = pReceivedData;
    numOfDataToReceive = nNumOfDataToReceive;
    numOfDataReceived = 0;
    readyToExchange = FALSE;
 
    UCSR0B |= (1 << RXCIE0) | (1 << RXEN0);
}
ISR(USART0_RX_vect)
{	
    *receivedDataPtr = UDR0;
    receivedDataPtr++;	
    numOfDataReceived++;
    if (numOfDataReceived == numOfDataToReceive)
    {
    	UCSR0B &= ~((1 << RXCIE0) | (1 << RXEN0));
        readyToExchange = 1;
    }
}

Осталось только все это как-то запустить. Идем в функцию main() и смотрим, что там у нас есть:

void main(void)
{
    sei();
    UBRR0 = AVR_USART_9600_VALUE;
 
    while(1)
    {
	UART_ReceiveData(testBuffer, 8);
	while(!readyToExchange);
	for (i = 0; i < 500; i++);
	UART_SendData(testBuffer, 8);
	while(!readyToExchange);
    }
}

Для начала тут у нас глобальное разрешение прерываний и настройка скорости передачи данных. Вся движуха у нас в цикле while(1). Вызываем команду на прием восьми байт данных и ждем, пока этот процесс завершится и взлетит флаг readyToExchange. После этого еще немножко тупим в задержке, чтобы прием окончательно завершился и запускаем передачу того массива данных, который мы только что приняли. Вот и все )

Осталось удостовериться, что все работает как надо. Открываем терминал (я, кстати, использую Advanced Serial Port Monitor). Передаем контроллеру 8 байт данных и он нам должен в ответ прислать те же самые байты.
Работа на прерываниях
Видим, что все удалось! Данные совпадают, программа работает правильно. На этом, собственно, на сегодня все, до новых встреч на нашем сайте!

Понравилась статья? Поделись с друзьями!

Микроконтроллеры AVR. UART. Использование прерываний.: 10 комментариев
  1. А насколько она правильно отработает, если я пошлю следом вторую строку. Получается что пока я не установлю флаг readyToExchange в 1, второе сообщение не уйдет.

    К примеру будут два сообщения
    void main(void)
    {
    sei();
    UBRR0 = AVR_USART_9600_VALUE;

    UART_SendData(«Start systems», 8); // Это уйдет

    UART_SendData(«initialization TWI», 8); — а это уже нет.

    while(1){

    }

    • Да, все верно. При таком варианте отправки надо после вызова UART_SendData анализировать флаг готовности и только потом начинать новую отправку.

  2. Добрый день!
    На старости лет полез в авр-ки… Черт меня дернул)))
    Никак с этими уартами не подружусь)))
    Объясните бестолковому, как принимать строки разной длины?
    Максимально 38 символов, минимально- 3. Последний всегда «;»
    Собственно, первые два- команда, потом параметр от 1 до 35 символов, последний символ «;». «Типа ID;» — запрос, ответ на него,например, «ID090;»
    Всю обработку уже написал с горем пополам))) А вот UARTы эти никак победить не получается.
    Полная задачка по уартам такая:
    есть встроенный- у него используется и прием, и передача. И один программный- только на прием.
    контроллер должен постоянно слушать оба приемных. С программного- тупо отправлять, безо всякого анализа, данные на передачу. С встроенного- тут идет обработка уже.
    Но если на приемных какое-то время (около секунды) ничего нет , то надо отправить запрос- четыре символа. Типа «IF;»
    Или иногда, по мере необходимости- что-то типа «PCxxx;»
    Как это можно реализовать?

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

  3. Честно говоря, уже пробовал. Ничего у меня толком не вышло.
    Дело в том, что, допустим, на запрос типа «IF;» мне должно прилететь IF и еще 35 значений, потом ; и из этого потока мне надо в разных местах навыдергивать сначала 5-ти значное десятичное значение, а потом еще 2-3 однозначных или односимвольных, из пакета с индефикаторами FA или FB- 5-ти значное, используется там же, где и из «иф». т.е. из FAxxxXXXXXxxx; только которые Х. ))) И они мне нужны как целое десятичное число))) Хорошо, хоть обрабатывать надо только то, что идет со встроенного UART). А тут еще и этот программный)))
    Просто, сам уже зациклился, наверное) Азы программирования проходил еще больше 30 лет назад) FORTRAN и PL/1)))) А с контроллерами работал в середине 90-х)))) Тогда за ночь осваивал какой-нибудь 580ВЕ31 или 8035))) Видимо, мозги уже ржаветь начали, работают со скрипом))))

  4. Я понимаю, что надо сделать как-то типа

    объявить массив uint8 rx_str[];

    ну и потом что-то типа

    { uint8 i=0;
    while (UDR!=»;») {
    rx_str[i]= UDR;
    i++;}
    rx_str[i]= UDR; // чтобы не потерять «;»- фактически rx_str[i]=»;»
    return i; }

    т.е. заполнить массив и получить количество символов, Хотя, оно мне особо и не нужно. Обрабатываться будет только в случае, если их или 14 (первые 2 FA или FB) или 38 (первые два IF), причем третий «0», а последний «;»
    А вот как правильно все оформить не въеду)))
    Ладно, не буду Вас мучить, лучше на выходные сгоняю на рыбалку))))

      • Добрый вечер!
        А рыбалка была в дельте Волги)))) А там всегда превосходная!))) Магическая рыбка вобла))))
        Даже калмыцкие дороги до конца всего позитива не испортили. Но диски придется купить другие- одно колесо можно просто выкинуть- диск разбит, скат порван.
        Но рыбалка была великолепной!))) Нас мой астраханский товарищ в такие места завез- чудо! Сейчас на берегах Волги и её «спутников»- камень кинь и в рыбака попадешь, а мы трое суток и людей особо не видели)))

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *