Урок 14
Часть 5
USART. Связь МК с ПК
Сегодня мы продолжим изучение программирования интерфейса USART.
В предыдущей части занятия мы научились передавать данные в ПК по шине USART. Сегодня мы попробуем их оттуда принять.
Что для этого нам потребуется? Для этого нам потребуется обработать прерывание, которое возникает при окончании заполнения буфера чтения, то есть когда пакет с данными был окончательно принят.
Давайте создадим новый проект, чтобы проект, который мы написали для передачи данных, у нас остался нетронутым. Проект назовём Test12 и полностью со всем кодом и файлами туда всё перенесём из предыдущего Test11, благо, делать мы это уже умеем неплохо. Поэтому процесс я этот здесь описывать повторно не буду, чтобы, как говорят, не засорять эфир.
Как мы видели в одной из прошлых частей в таблице, данное прерывание имеет обозначение RXC и вектор 12 или 0x0B.
По идее, нам даже не надо заботиться о векторах и их номерах, всё это за нас сделает библиотека interrupt.h, которой мы также пользовались, когда использовали таймер. Но ради любопытства мы всё же найдём описание в библиотеке, заодно и точное имя функции. Откроем файл interrupt.h и… ничего такого там не увидим. Там совсем другие вещи, а именно способы представления строки векторов и прочие мудрёные прибамбасы. А уже зайдя в io.h, также подключенному к interrupt.h, найдём вот такую строку
#elif defined (__AVR_ATmega8A__)
# include <avr/iom8a.h>
которая говорит нам о том, что если мы используем контроллер, который указан в скобках, то у нас подключится файл iom8a.h, в котором мы всё и найдём. Зайдём в него и найдём там такие строки
/* USART, Rx Complete */
#define USART_RXC_vect _VECTOR(11)
#define USART_RXC_vect_num 11
Вот это нам и нужно. Правда вектор тут не 12, как в таблице, а 11. Ну, видимо, или ошибка где-то, или эти векторы можно как-то переопределять. Не будем заморачиваться по этому поводу, а возьмём в буфер обмена имя функции-обработчика и вернёмся в наш главный файл Test12.c и напишем функцию-обработчик перед функцией main(), пока, соответственно, с пустым телом
//—————————————-
ISR(USART_RXC_vect)
{
}
//—————————————-
int main(void)
Но это ещё не всё. Чтобы нам увидеть и оценить приём данных из ПК, нам как-то это надо ощутить. И поможет нам в этом символьный дисплей, который мы подключали в уроке 12. Скопируем в проект из проекта урока 12 файлы lcd.c и lcd.h, подключим их в дерево проекта и файл lcd,h подключим в файл main.h
#include «lcd.h»
#include «usart.h»
Из того же проекта скопируем инициализацию порта D и напишем её до функции-обработчика в файле Test12.c
//—————————————-
void port_ini(void)
{
PORTD=0x00;
DDRD=0xFF;
}
//—————————————-
ISR(USART_RXC_vect)
Код вывода слова «Ok!» на ПК вернём из бесконечного цикла в основное тело функции main().
В результате со всем сборным ходом функция main() у нас будет вот такая
//—————————————-
int main(void)
{
port_ini(); //Инициализируем порты
LCD_ini(); //Инициализируем дисплей
USART_Init (8); //115200
USART_Transmit('O');//Передаем при включении
USART_Transmit('k');//сообщение «Ok!», что свидетельствует
USART_Transmit('!');//о правильно работе программы
USART_Transmit(0x0d);//переход в начало строки
USART_Transmit(0x0a);//переход на новую строку
while(1)
{
}
}
Ну и также, я думаю, что все мы помним, что кроме чем описать и применить функцию-обработчик прерывания, для того, чтобы прерывания работали, нам необходимо разрешить ещё и глобальные прерывания. Вызовем соответствующую функцию в main()
USART_Transmit('O');//Передаем при включении
sei();
Теперь посмотрим. как всё подключено. Дисплей я подключил точтно таким же способом, как и в уроке по нему и теперь у нас к плате подключен и дисплей и переходник USB-USART
Вот такая картина получилась, соответственно, после того, как проект, который мы создали уже был собран и прошит. Как мы видим, на дисплее красуются 32 белых ярких прямоугольника. Чтобы такое безобразие исключить, нам желательно дисплей очистить. Только мы функцию очистки в 12 уроке написали, а прототип на неё в файл lcd.h не добавили. Давайте это сделаем
void LCD_ini(void);
void clearlcd(void);
Теперь мы данную функцию свободно можем вызвать из файла main.c
USART_Transmit(0x0a);//переход на новую строку
clearlcd();
while(1)
Также в силу того, что на дисплей мы информацию будем выводить по одному символу, то прототип для функции sendchar мы пропишем
void clearlcd(void);
void sendchar(unsigned char c);
Теперь соберём код и прошьём контроллер. Дисплей должен будет очиститься.
Ну и, в принципе у нас всё готово теперь для того, чтобы писать исходный код функции-обработчика прерываний.
Создадим переменную и запишем в неё значение из буфера USART
ISR(USART_RXC_vect)
{
int b;
b = UDR;
И чтобы по пришествии очередного байта с кодом символа мы могли его отображать не в ту же позицию дисплея, а в следующую, мы создадим две переменные горизонтальных и вертикальных координат символа на дисплее. Создадим мы их в начале нашего файла Test12.c
#include «main.h»
//—————————————-
unsigned char x=0,y=0;
//—————————————-
Теперь напишем в обработчике вывод символа и расчёт координат для следующего символа, учитывая количество символов по вертикали и горизонтали. Для нас это уже простой код, так как мы уже много чего изучили, поэтому объяснять я его не буду
b = UDR;
setpos(x,y);
sendchar(b);
if(x<15) x++;
else
{
x=0;
if(y==0) y=1;
else if(y==1) {y=0;clearlcd();}
}
}
Ну вот, в принципе и весь код.
Давайте его соберём и прошьём контроллер и попробуем ввести из терминала какой-нибудь символ, например '1'. Делается это легко. В терминале для этого есть специальная строка внизу
Но просто ввести недостаточно, чтобы данные отправились в порт. Нужно либо нажать кнопку «-> Send«, либо на клавиатуре клавишу «Enter«
После полного ввода нашего символа мы видим, что символ нормально пришел и отобразился на дисплее
Введём теперь по очереди ещё два символа. Всё работает
Но, как оказалось, успокаиваться на этом рано. Давайте попробуем ввести в терминальную программу длинную строку и передать её в контроллер. Чтобы видеть, что всё дошло, давайте передадим вот такую вот упорядоченную строку из 31 символа «1234567890123456789012345678901». Отправляем строку и видим, что символы дошли не все, и не просто не все, а мало какие символы вообще дошли
Я думаю, здесь не сложно догадаться, почему такое происходит. А происходит это потому, что время вывода на экран символа намного больше, чем время от прихода по USART одного символа до прихода другого. То есть для работы с дисплеем по приёму непрерывных строк из USART скорость 115200 будет очень велика. Попробуем её убавить пока до 57600 бит в секунду. Для этого мы нажмем Disconneсt в терминальной программе и переключимся в ней на такую скорость
Здесь переключили, теперь откроем таблицу в технической документации на контроллер и узнаем число, которое мы должны передать в функцию инициализации USART
Исправим аргумент в вызове функции
USART_Init (16); //28800
Соберем код, нажмём «Connect» в терминале, прошьём контроллер, и отправим ещё раз ту же строку. Очень хорошо, что после отправки строка в терминале не стирается. Достаточно поставить в неё курсор и нажать Enter или кноку Send. Мы видим, что символов пришло больше, но не все
Таким же образом убавим скорость до 28800. Узнаем число
Исправим в коде
USART_Init (34); //28800
Пересоберём код, соединимся в терминале с портом и отправим строку, дописав в неё даже ещё цифру 2
Мы видим, что теперь у нас на дисплее почти всё отображается. Когда я писал видеоурок, я не заметил, что всё-таки одна цифра у нас пропущена. В нижнем ряду не хватает тройки. Таким образом. оптимальная и максимальная скорость для приёма непрерывных строк и отображения их на дисплее будет всё-таки 19200 bps. При такой скорости будет всё нормально. Можно конечно было ещё поиграть с таймингами в выводе символов в файле lcd.h и добиться вывода всех символов при скорости 28800, но для строк, я думаю достаточно и 19200. Соответственно, вы можете попробовать свои скорости либо даже поиграть с добавлением дополнительного буфера в оперативной памяти контроллера, и отправкой строк сначала туда, а затем уже в другой какой-то функции на дисплей и написать в комментариях, что у вас получилось. Писать можно либо здесь, либо под видеоуроком на Youtube.
На этом работа с USART у нас закончена. Но использовать его мы на этом не прекращаем. Будет ещё много уроков, в котором мы им ещё воспользуемся.
Предыдущая часть Программирование МК AVR Следующий урок
Программатор и переходник USB-TTL можно приобрести здесь:
Программатор (продавец надёжный) USBASP USBISP 2.0
Переходник USB-TTL лучше купить такой (сейчас у меня именно такой и он мне больше нравится)
Смотреть ВИДЕОУРОК (нажмите на картинку)
Подскажи пожалуйста как отправить по UARTу данные АЦП
для контроля АКБ по SMS.
Обычно, преобразовать в строку и отправить функцией USART_TX, которую мы написали в 39 уроке.
По кусочку кода непонятно. И как именно не работает. Вообще дисплей не работает, или счётчик не считает, т.е. показывает одну только цифру, какой именно у Вас дисплей, какой в нём контроллер.
Совсем запутался,наверно серое вещество высохло,
сейчас программу чищу.
Мне не на дисплей выводить даннные надо,
а на SIM-модуль для отправки по SMS.
MK — ATmega32
дисплей — WG12864B
контроллер — KS0107
Может скинуть исходник?
Текст на дисплей приходит весь, но не важно 1 символ и слово отправляю в конце приходит например
1==
Hello==
Hello My Dear Fr
iend==
Прошу прощения это были два символа 0x0d и 0x0a
Спасибо за данный урок. Замучился уже с одним проектом. Или ресурсов Attiny 2313A не хватает, или компилятор своеобразно себя ведёт. Решил разбить на две задачи и установить две Attiny 2313A в устройство, связав их через UART.
Мы не изучали функцию очистки дисплея, но я ее сам написал
void clearLCD(void)
{
sendbyte(0b00000001, 0);//Очистка дисплея
_delay_ms(2);
}
В даташите нашел, кстати спасибо за такие подробные описание, всегда хотел научится понимать даташит. Сейчас с вами научился немного. Спасибо!!!
Добрый день! Как не менял скорость выводит три первых символа и последний. В чём может быть дело?
Спасибо!
Даже не знаю, что и сказать. Пробуйте другой переходник, другой контроллер, другой порт. У меня нет конкретного ответа. Это надо находиться на месте и смотреть, но у меня, к сожалению, такой возможности нет.
Значит дело в железе, так и думал. Спасибо Вам за уроки!!!
Добрый день! Я сам дурень, в таймингах дисплея написал милисекунды вместо микросекунд! А Вам спасибо за уроки!
Доброго времени суток!
Правильно я понял что у Atmega8 1 общий регистр на передачу и прием?или я ошибаюсь (оба UDR называются)
В PIC например есть 2 отдельных регистра
Да оба называются UDR.
А физически там 2 регистра.
Доброго времени суток .
Прежде всего спасибо за уроки (просмотрел весь курс по AVR и многому научился) ! Теперь собственно главное о чем бы хотел вас спросить: есть необходимость чтобы MK в частности Atmega8 принимала не один, а 2 или 3 байта данных . Как все устроено с одним байтом предельно понятно (байт пришёл и МК вошел в прерывание где я его и считал в глобальную переменную) . Но как быть если я посылаю два или три байта . Каким образом можно реализовать данную возможность ?
Я могу предположить что припрееме первого из 2 или трех байтов нужно выходить из вектора прерываний передавать присланные данные и затем по пришествии второго байта опять уходить в прерывания но реализовать грамотна у меня не получилось данную задумку .
Прерывание настраивается только на 1 байт, поэтому когда идёт серия, надо применять какие-нибудь концевые байты, для этого и существуют протоколы. А в прерывании уже следить, а не концевой ли байт к нам пришёл.
Спасибо за пояснение по поводу протокола переда данных, но я спрашивал немного не то (видимо надо было подробнее пояснять) . Мне непонятно как реализовать саму возможность приема 2-х или 3-х байт одного за другим разом с ПК в одну переменную (скажем формата uint32_t) . Насколько я понимаю регистр UDR может принять 8 бит или один байт ( при UCSRB|=(0<<UCSZ2) , UCSRC|=((1<<UCSZ1)|(1<<UCSZ0)) ) а значит при активации вектора прерываний ISR(USART_RXC_vect) я получаю за раз только один байт в объявленную мной ранее глобальную переменную переменную . Можно ли как то так исхитрится чтобы я смог записать в одну переменную 2 — 3 байта отправленных разом один за другим с ПК используя специальное ПО (к примеру Terminal1_9_b или еще что) . Сам голову сломал но нечего дельно придумать не получилось.
Вопрос снят (воплотил что хотел) .
На скоростях 115200 и 57600 выводит более-менее нормально символы. Не каждый раз, но выводит. А вот на меньших скоростях — сплошная абракадабра))
А, нет, это я не отключал программатор от отладочной платы, из-за него, должно быть. Хотя, 31 символ приходит нормально, вот 32 — абракадабра))
А если у меня 6 разрядный семисегментный индикатор ? как тогда прописать код ? подскажите пожалуйста !!!!! заранее спасибо !