Продолжаем работать с микроконтроллерами 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); UART_SendData(testBuffer, 8); while(!readyToExchange); } }
Для начала тут у нас глобальное разрешение прерываний и настройка скорости передачи данных. Вся движуха у нас в цикле while(). Вызываем команду на прием восьми байт данных и ждем, пока этот процесс завершится и взлетит флаг readyToExchange. После этого запускаем передачу того массива данных, который мы только что приняли. Вот и все )
Осталось удостовериться, что все работает как надо. Открываем терминал (я, кстати, использую Advanced Serial Port Monitor). Передаем контроллеру 8 байт данных и он нам должен в ответ прислать те же самые байты.
Данные совпадают, программа работает правильно. На этом, собственно, на сегодня все, до новых встреч на нашем сайте!