Реализуем подобие DMA на микроконтроллерах AVR
Всем привет. Довелось мне писать довольно большой проект на AVRках. Как известно у них не очень большие скорости 16 МГц, у тех же STM32 можно гнать 72МГц и выше. Но опыта на STM мало, по этому пока AVR. Так вот мне нужно было в проекте передавать данные по UART, посылки не большие 10-15 байт, скорость 9600. Если все это дело реализовать в основном цикле, то это очень сильно тормозит систему. А у меня помимо этого есть еще куча других задач. Единственный выход использовать прерывания. Посмотрел несколько примеров в интернете, некоторые из них сложные, другие я даже не понял как работают, и по итогу сделал как понимал, и теперь делюсь с вами.
И так в первую очередь нам необходимо сформировать то что хотим отправить, то есть получить буфер для отправки, не важно как sprintf или itoa. Допустим определим буфер:
char sprintf_buf [15];
Теперь создаем еще один буфер из которого в UART будут отправляться данные, так как данные в первом буфере могут изменятся в произвольный момент времени, и это может изменить данные для отправки.
char uart0_tx_buf [20];
Теперь напишем функцию которая будет переписывать данные из одного буфера в другой. Тут есть момент, если вы пользуетесь strcpy то она при копировании строки в буфер обязательно добавить в конце 0, что и будет свидетельствовать о том что это последний символ. Но если у вас данные в буфере теоретически могут хранить число 0 не в символьном виде то лучше воспользоваться специальным символом. Я решил использовать '$'. Буфер будет переписывать пока не встретит символ '$' и сразу остановиться. Функция выглядит так:
void uart0_send_string ( char *send0) // сюда передаем строку в конце которой символ $
{
if (uart0_tx_counter == 0) // если счетчик на нуле можем обновить буфер, если нет то пропускаем
{
for (int i = 0; i < 20; i ++) // забиваем буффер отправки символами из переданого буфера
{
uart0_tx_buf[i] = send0[i];
if (uart0_tx_buf[i] == '$') break; // пока не встретим символ $. но его мы тоже заносим
}
UDR0 = uart0_tx_buf[uart0_tx_counter]; // отправляем нулевой байт UART
uart0_tx_counter ++; // увеличиваем счетчик
UCSR0B |= ( 1<<UDRIE0); // включаем прерывание по опустрошению буффера передачи
}
}
Вкратце что тут происходит. Допустим мы уже заполнили наш буфер на передачу sprintf_buf и теперь вызываем функцию uart0_send_string ( sprintf_buf ), передавая ей наш буфер. Как вы могли заметить есть еще какой то счетчик: uart0_tx_counter. Он нужен для нескольких задач, первая если мы попробуем отправить данные пока UART не закончил передачу, у нас ничего не получится, так как if(uart0_tx_counter == 0) нас тупо не пустит. А во вторых это счетчик позволяет знать UARTу какой элемент буфера нужно отправлять.
Еще я забыл рассказать про главное, а именно инициализацию UART. Не буду сильно расписывать, предполагается что вы знаете как настраивать скорость, прерывания, и просто включение:
void init_Uart_0 (void)
{
UCSR0B=(1<<RXEN0)|( 1<<TXEN0); //Включаем прием и передачу по USART
UCSR0B |= (1<<RXCIE0); //Разрешаем прерывание при приеме
UCSR0C = (1<<UPM01)|(1<<UCSZ01)|(1<<UCSZ00); //паритет 1, 8 бит
}
Как видим прерывание при передаче (а точнее по опустошению буфера передачи UCSR0B |= ( 1<<UDRIE0)) тут не включаем, иначе все поломается. А включаем прерывания только после того как отправили 0 байт по UARTу. Что произойдет дальше, мы запустили функцию которая переписала в буфер UARTа данные, отправила нулевой байт и вышла. Теперь мы можем заниматься своими делами, до тех пор пока не сработает прерывания по опустошению буфера. Вот его нужно обработать.
//----------обработчики прерываний на передачу ----------//
ISR (USART0_UDRE_vect) // прерывание по опустошению буффера передачи
{
if(uart0_tx_buf[uart0_tx_counter] != '$') // пока символ в буффере не равен $
{
UDR0 = uart0_tx_buf[uart0_tx_counter]; // продолжаем копировать из буффера байты в юарт
uart0_tx_counter ++; // ну и естественно передвигаемся по массиву
}
else // если дошли до символа $
{
uart0_tx_counter = 0; // сбрасываем счетчик
UCSR0B &= ~ ( 1<<UDRIE0); // вырубаем прерывания по опустошению буфера
}
}
Тут логика похожая, зашли в прерывания проверили символ, если это не '$' то записываем символ, увеличиваем счетчик и снова выходим из прерывания. У нас опять есть куча свободного времени чтоб делать свои дела. И так будет до тех пор пока прерывание не встретит '$'. Тогда мы сбрасываем счетчик, выключаем прерывание и готовы ждать новую посылку.
Что дает '$', это позволяет отправлять посылки разной длины (главное чтоб в буфер влезло). Если посылка фиксирована то можно сделать еще проще, но я делал универсальный код.
Вообщем сильно не пинайте, это мой первый пост. Конструктивной критике рад:)