Как стать автором
Обновить

Реализуем подобие DMA на микроконтроллерах AVR

Время на прочтение3 мин
Количество просмотров6.9K

Всем привет. Довелось мне писать довольно большой проект на 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);                        // вырубаем прерывания по опустошению буфера
     }
}

Тут логика похожая, зашли в прерывания проверили символ, если это не '$' то записываем символ, увеличиваем счетчик и снова выходим из прерывания. У нас опять есть куча свободного времени чтоб делать свои дела. И так будет до тех пор пока прерывание не встретит '$'. Тогда мы сбрасываем счетчик, выключаем прерывание и готовы ждать новую посылку.

Что дает '$', это позволяет отправлять посылки разной длины (главное чтоб в буфер влезло). Если посылка фиксирована то можно сделать еще проще, но я делал универсальный код.

Вообщем сильно не пинайте, это мой первый пост. Конструктивной критике рад:)

Теги:
Хабы:
Всего голосов 19: ↑9 и ↓10-1
Комментарии28

Публикации

Истории

Работа

Программист C++
117 вакансий
QT разработчик
9 вакансий

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург