AVR. 4. Работа с UART на примере Atmega16A
Ранее мы уже рассмотрели, как дёргать ножки и опрашивать кнопки, сейчас познакомимся с одним интерфейсом микроконтроллера, который позволит нам его сопрячь с компьютером и различной периферией, которая этот интерфейс поддерживает.
UART — один из самых распространёны и удобных в работе портов. Хоть его первоначальную разновидность в виде RS-232C уже сложно найти, то его родственники встречаются повсеместно, как в виде промышленных проводных гигантов, таких, как RS485. В микроконтроллерной технике он соединяет различные узлы, которые обмениваются большими объёмами данных, например: МК, датчики, модули беспроводных и проводных интерфейсов…
Связь Atmeg’и16 с ПК по UART
В свою очередь, UART расшифровывается, как «универсальный асинхронный приемник и передатчик» («Universal Asynchronous Receiver and Transmitter»). Рассмотрим его по подробнее.
В статье мы организуем связь с компьютером по средством одного из следующих вариантов: RS-232C, USB или Bluetooth; для первого варианта нам понадобиться RS232-UART преобразователь интерфейса на MAX232, для современного способа соответственно USB-UART преобразователь на cp2102 или ft232, есть и другие варианты таких преобразователей; для связи без проводов по Bluetooth один из следующих адаптеров: BTM-111 или WT-41, SPBT2532C2.AT2, или один из дешевых китайских блюпупов HC-04, HC-05…; или любой другой канал связи c UART интерфейсом. В статье же буду рассказывать на примере cp2102.
Соберем схему из микроконтроллера, в нашем случае Atmega16A, преобразователя интерфейса, т.к. его вариантов привёл много – выберите любой, схемы на сайте приведены. Для индикации принятых пакетов у нас будет мигать светодиод подключенный к PC0 через ограничительный резистор в 270-680 Ом.
Схема
Собранная схема на макетной плате без пайки приведена в начале статьи.
В примере будем принимать однобайтовые пакеты и отсылать их обратно увеличенными на единицу, также о проделанной операции будет уведомлять диодом.
Начнём с инициализации последовательного порта.
Для начала рассчитаем и пропишем – с какой скоростью будет общаться наш камушек. Для этого нужно рассчитать UBRR. Для его расчёта есть 3 формулы, которые зависят от выбранного режима работы порта:
Асинхронный Нормальный режим (U2X=0) |
|
Асинхронный Удвоенная скорость(U2X=1) |
|
Синхронный режим |
У нас при встроенном 8МГц осцилляторе в нормальном режиме
Полученное значение нужно прописать в 8-битные регистры UBRRL и UBRRH, в первый младшие 8 бит скорости, во второй старшие 2 бита, в нашем случае всё уместилось в младшем регистре, в старший пишем 0.
Кроме скорости существуют ещё 3 регистра конфигурации порта – UCSRA, UCSRB, UCSRC.
7 | RXC (Receive Comlete) | Бит завершения приёма. Автоматически устанавливается при приеме данных, сбрасывается при чтение. |
6 | TXC (Transmit Comlete) (только запись) |
Бит завершения передачи. Автоматически устанавливается после отправки кадра данных, если следующих для отправки данных нет. |
5 | UDRE (UDR Empty) | Бит отсутствия данных для отправки. Установлен, если данных для отправки нет. |
4 | FE (Frame Error) | Ошибка кадра. 1-если ошибка в формате принятого пакета. |
3 | DOR (Data OverRun) | Переполнение буфера. 1-если буфер переполнен. |
2 | PE (Parity Error) | Ошибка чётности. 1-если была ошибка. |
1 | U2X (Double speed) (только запись) |
Удвоения скорости, см установка битрейна выше. |
0 | MPCM (Multi-Processor Communication Mode) (только запись) |
Работа в многопроцессорном режиме. |
7 | RXCIE (Receive Comlete Interrupt Enable) | Бит прерывания приёма данных |
6 | TXCIE (Transmit Comlete Interrupt Enable) | Бит прерывания завершения передачи |
5 | UDRIE (UDR Empty Interrupt Enable) | Бит прерывания отсутствия данных для передачи |
4 | RXEN (Receiver Enable) | Разрешение приёма данных |
3 | TXEN (Transmitter Enable) | Разрешение передачи данных |
2 | UCSZ2 (USART Character SiZe 2) | См. ниже UCSZ1 и UCSZ0 в UCSRC |
1 | RXB8 (Receive Data Bit 8 ) (только чтение) |
9 бит принятых данных, в многопроцессорном режиме |
0 | TXB8 (Transmit Data Bit 8 ) (только запись) |
9 бит отправленны данных, в многопроцессорном режиме |
7 | URSEL (USART Register SELect) | Всегда равен 1. Служит для выбора регистра UCSRC, иначе UBRRH. |
6 | UMSEL (USART Mode SELect) | Режим работы. 1 — синхронный, 0 — асинхронны |
5-4 | UPM1-UPM0 (USART Parity Mode) | Установка режима чётности. UPM1:0=1:1 – нечётный UPM1:0=1:0 – чётный UPM1:0=0:0 – нет контроля |
3 | USBS (USART Stop Bit Select) | Количество стоп-битов. 0-1 стоп бит, 1-2 стоп бита. |
2-1 | UCSZ1-UCSZ0 (USART Character SiZe) | UCSZ2-0 – устанавливают размер кадра, см таблица ниже. |
0 | UCPOL (USART Clock POLarity) | Используется в синхронном режиме. 0-данные отправляются по переднему фронту 1-по заднему фронту |
UCSZ2-UCSZ0 Установка размера кадра
Пример инициализации UART
int init_UART(void) { // Установка скорости 9600 UBRRH=0; // UBRR=f/(16*band)-1 f=8000000Гц band=9600, UBRRL=51; // нормальный асинхронный двунаправленный режим работы // RXC - завершение приёма // |TXC - завершение передачи // ||UDRE - отсутствие данных для отправки // |||FE - ошибка кадра // ||||DOR - ошибка переполнение буфера // |||||PE - ошибка чётности // ||||||U2X - Двойная скорость // |||||||MPCM - Многопроцессорный режим // 76543210 UCSRA=0b00000000; // RXCIE - прерывание при приёме данных // |TXCIE - прерывание при завершение передачи // ||UDRIE - прерывание отсутствие данных для отправки // |||RXEN - разрешение приёма // ||||TXEN - разрешение передачи // |||||UCSZ2 - UCSZ0:2 размер кадра данных // ||||||RXB8 - 9 бит принятых данных // |||||||TXB8 - 9 бит переданных данных // 76543210 UCSRB=0b00011000; // разрешен приём и передача по UART // URSEL - всегда 1 // |UMSEL - режим:1-синхронный 0-асинхронный // ||UPM1 - UPM0:1 чётность // |||UPM0 - UPM0:1 чётность // ||||USBS - топ биты: 0-1, 1-2 // |||||UCSZ1 - UCSZ0:2 размер кадра данных // ||||||UCSZ0 - UCSZ0:2 размер кадра данных // |||||||UCPOL- в синхронном режиме - тактирование // 76543210 UCSRC=0b10000110; // 8-битовая посылка }
Чтении и запись производятся из регистра UDR.
Отправка байта данных выглядит следующим образом:
void send_Uart(unsigned char c)// Отправка байта { while(!(UCSRA&(1< <UDRE))) // Устанавливается, когда регистр свободен {} UDR = c; }
Для удобства работы в проекте также представлены функции:
send_Uart_str — для отправки целых строк текста
void send_Uart_str(unsigned char *s)// Отправка строки { while (*s != 0) send_Uart(*s++); }
и send_int_Uart – для отправка чисел от 0000 до 9999
void send_int_Uart(unsigned int c)// Отправка числа от 0000 до 9999 { unsigned char temp; c=c%10000; temp=c/100; send_Uart(temp/10+'0'); send_Uart(temp%10+'0'); temp=c%100;; send_Uart(temp/10+'0'); send_Uart(temp%10+'0'); }
Приём байта данных:
unsigned char getch_Uart(void)// Получение байта { while(!(UCSRA&(1< <RXC))) // Устанавливается, когда регистр свободен {} return UDR; }
Повторюсь – проект простенький: при старте после инициализации порта выводим небольшую строчку и число. Далее в бесконечном цикле ждём прихода данных, как получаем – сразу отправляем их обратно, но модифицированные, модификация сводиться к тому, что к каждому принимаемому байту добавляем единицу и отсылаем обратно отправителю, сигнализируем светодиодом об обработанном пакете.
Основная функция:
int main(void) { DDRC = 0b00000001; // инициализация порта C, PC0 - выход init_UART(); // инициализация UART _delay_ms(1000); // задержка 1c send_Uart_str("alex-exe.ru");// отправка строки send_Uart(13); // перенос строки send_int_Uart(2013); // отправка числа send_Uart(13); // перенос строки while(1) // бесконечный рабочий цикл { if(UCSRA&(1< <RXC)) // если пришёл байт по UART { send_Uart(getch_Uart()+1); // отвечаем увеличенным на 1 пришедшим байтом // LED on // и мигнём диодом PORTC = 0b00000001; // Включаем диод PC0 = 1 = Vcc _delay_ms(50); // задержка 50мс // LED off PORTC = 0b00000000; // Выключаем диод PC0 = 0 = Vcc } } }
Проект в proteus будет выглядеть следующим образом:
Проект в proteus
В рамках следующего урока будет рассмотрен .
При написании статьи использовался datasheet на Atmega16.
Alex_EXE | 02.04.2013 | AVR |
глеб пишет 11.03.2014 в 20:44 #
Превосходно! это то, что я искал несколько дней 🙂 вы очень помогли
Александр пишет 02.10.2014 в 14:49 #
Очень познавательно. Побольше бы таких статей.
vv8 пишет 20.01.2015 в 20:03 #
Спасибо за отличное изложение!
Multidos пишет 13.02.2015 в 23:12 #
Отлично разжевано, спасибо, наконец понял что к чему +1
Ваха пишет 03.03.2015 в 00:04 #
Добрый вечер! Большое спасибо за вашу статью, все понятно и доступно изложено! У меня вопрос. У меня atmega8a.
#define F_CPU 8000000UL
#define BAUD 9600
#define MYUBRR (((F_CPU / (BAUD * 16UL))) — 1) // скорость 1200
#define MYUBRR (((F_CPU / (BAUD * 64UL))) — 1) // скорость 4800
void USART_Init( unsigned int ubrr)//Иниц модуля USART
{
UBRRH = (unsigned char)(ubrr>>8);
UBRRL = (unsigned char)ubrr;
UCSRA |= (1<<U2X); // Удваеваем скорость что бы получить 9600
UCSRB |= (1<<RXCIE)|(1<<RXEN)|( 1<<TXEN);
UCSRC = (1<<URSEL)|(0<<USBS)|(3<<UCSZ0);
}
USART_Init(MYUBRR);
Пробовал в протеусе — скорость правильная, как и должно по документации, а на практике совсем другая скорость. в чем дело?
Ваха пишет 04.03.2015 в 13:34 #
Добрый день всем! Разобрался, нужно было выставить fuse при прошивке в программаторе. Я считал, что если в программе указываю #define F_CPU 8M, то МК работает на этой частоте. Всем удачи!
kitkit пишет 09.06.2015 в 03:00 #
жаль, что автор забил на идею создания подробных уроков…
Николай пишет 01.01.2016 в 19:34 #
#define F_CPU 16000000L
UBRRH=0; // UBRR=f/(16*band)-1 f=16000000Гц band=9600,
UBRRL=103; // нормальный асинхронный двунаправленный режим работы
Остальное скопировал у вас.
‘UBRRH’ undeclared (first use in this function)
‘UBRRL’ undeclared (first use in this function)
‘UCSRA’ undeclared (first use in this function)
и так далее, все, что касается регистров МК такая же ошибка. Помогите, пожалуйста, что не так.
Alex_EXE пишет 08.01.2016 в 23:30 #
Среда разработки AtmelStudio?
Библиотеки подключены? Пути верны?
I пишет 20.03.2016 в 21:18 #
Спасибо автору. Благодаря примеру нашел ошибки в своем коде.
😉
sardex пишет 26.03.2016 в 21:27 #
Ни че тут не понял. Но когда поумнею начну писать более адекватные коменты. Автор пиши есчЁ (хотя эта фраза помоему уже заезжена, но все равно ПИШИ).
Дмитрий пишет 03.04.2016 в 20:37 #
Черт побери, 10 минут думал над строкой
send_Uart(temp/10+’0′);
пример, когда наглядность становится врагом понятности))
Максим пишет 01.11.2016 в 01:22 #
Добрый день.
Всё получилось в «Proteus» но программа не работает с реальным микроконтроллером. У меня: atmega16, PL2303HX и Terminal 20.
Alex_EXE пишет 05.11.2016 в 16:36 #
Замкните выводы Rx Tx PL2303HX — проверьте его работу.
Проверьте работу МК на других более простых примерах.
Проверьте настройки фьюзов.
Владимир пишет 18.11.2017 в 23:01 #
Здравствуйте, в Си почти что ламер. Почему функция send_Uart(13); дает переход на строку ниже? Почему 13 а не 25 или 18 например?
Alex_EXE пишет 19.11.2017 в 16:54 #
13 (0Ch) — это ASCII код символа переноса строки, см ASCI кодировку. Ещё примеры: 9 (09h) код символа табуляции, 48 (30h) код символа 0, 255 (FFh) код символа я.
Андрей пишет 27.01.2019 в 15:20 #
Добрый день!
Вы UBRR рассчитываете при условии, что у вас встроенный 8МГц осциллятор в нормальном режиме микроконтроллера.
Однако в datasheet на микроконтроллер Atmega8 в разделе
8. System Clock and Clock Options подраздел 8.3 Default Clock Source сказано что тактовая частота по умолчанию у микроконтроллера 1 МГц. (The device is shipped with CKSEL = “0001” and SUT = “10”. The default clock source setting is therefore the 1 MHz Internal RC Oscillator with longest startup time.)
Прошу пояснить, почему при расчёте используется 8МГц, а не 1МГц согласно datasheet и какой тогда использовать показатель тактовой частоты на МК Atmega16A (я использую данный микроконтроллер в своих начинающих опытах) ?
Заранее Вам благодарен!
Андрей пишет 27.01.2019 в 15:23 #
Пардон, ошибся, у Вас как раз в статье Atmega16A, но суть вопроса от этого не меняется, так как в datasheet на данный МК частота по умолчанию также 1 МГц
Alex_EXE пишет 29.01.2019 в 03:37 #
Помоему там стоит по умолчанию 8МГц. Или во время прошивки данные фьюзы поправил.
Редко и давно работал с Atmel, уже точно не помню.
В коде 9 строка F_CPU 8000000UL стоит определение частоты подключенного/настроенного осцилятора для переменных delay, т.е. используемый МК у меня был настроен на 8МГц.
Георгий пишет 24.05.2019 в 21:13 #
А как быть с тем, что строка
«38. UCSRC=0b10000110; // 8-битовая посылка» нагло пишет еще и в UBRRnH (параллельно-одновременно)?
И симуляторе Студии Mega32 и Mega48 это видно.
И что там будет со скоростью?
denya241 пишет 02.04.2020 в 08:49 #
Георгий понимаю что не актуально уже но в Атмел 7 даже не видно что пишет в UCSRC но если в H то видно. Где-то уже жаловались на эту проблемку просто программа так отрабатывает. Просто у них один адресс и при 7бите в 1 все автоматом уходит в С.
Александр пишет 28.07.2021 в 01:47 #
Здравствуйте, вопрос по программированию МК в Atmel studio.
Вопрос такой:интересует алгоритм написания функции для МК AVR 32,
отправка показаний датчика через GSM модуль (SIM800C) по СМС на мобильник,
например:напряжение с АЦП(зарядное для акб.),
или запрос с мобильного баланса GSM модуля,
есть функция отправки смс с заранее написанными смс,
не могу понять как переделать функцию ,чтобы отправлять по смс ,
постоянно меняющиеся данные с датчика или запрос о балансе,
вот функция USART
void USART_SendString(char *str) /* Отправить строку функции данных USART */
{
int i=0;
while (str[i]!=0)
{
USART_TxChar(str[i]); /* Отправить каждый символ строки до NULL */
i++;
}
}
вот функция отправки смс:
void GSM_Send_Msg(char *num,char *sms)
{
char sms_buffer[35];
buffer_pointer=0;
sprintf(sms_buffer,»AT+CMGS=\»%s\»\r»,num);
USART_SendString(sms_buffer); //*отправить команду AT+CMGS=»Mobile No.»\r */
while(1)
{
if(buff[buffer_pointer]==0x3e) //*ждать символа ‘>’*/
{
LCD_String_xy(1,0,»OTPRAVL1″);
_delay_ms(3000);
LCD_Clear();
buffer_pointer = 0;
memset(buff,0,strlen(buff));
USART_SendString(sms); //* отправить сообщение на указанный nom. */
USART_TxChar(26);
USART_TxChar(0); //* отправьте Ctrl + Z, тогда будет передаваться только сообщение*/
break;
}
buffer_pointer++;
}
buffer_pointer = 0;
memset(buff,0,strlen(buff));
memset(sms_buffer,0,strlen(sms_buffer));
}
если не трудно объясните,что нужно дописать,
или скиньте ссылку на эту тему.
YURI пишет 26.12.2022 в 21:07 #
как передать в USART два байта для двух разных переменных