AVR. 4. Работа с UART на примере Atmega16A

Ранее мы уже рассмотрели, как дёргать ножки и опрашивать кнопки, сейчас познакомимся с одним интерфейсом микроконтроллера, который позволит нам его сопрячь с компьютером и различной периферией, которая этот интерфейс поддерживает.

UART — один из самых распространёны и удобных в работе портов. Хоть его первоначальную разновидность в виде RS-232C уже сложно найти, то его родственники встречаются повсеместно, как в виде промышленных проводных гигантов, таких, как RS485. В микроконтроллерной технике он соединяет различные узлы, которые обмениваются большими объёмами данных, например: МК, датчики, модули беспроводных и проводных интерфейсов…

Связь Atmeg’и16 с ПК по UART

Связь 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)
расчёт скорости работы UART
Асинхронный
Удвоенная скорость(U2X=1)
расчёт скорости работы UART
Синхронный режим расчёт скорости работы UART

У нас при встроенном 8МГц осцилляторе в нормальном режиме

UBRR=8000000/(16*9600)-1=51.0833 , округляем = 51.

Полученное значение нужно прописать в 8-битные регистры UBRRL и UBRRH, в первый младшие 8 бит скорости, во второй старшие 2 бита, в нашем случае всё уместилось в младшем регистре, в старший пишем 0.

Кроме скорости существуют ещё 3 регистра конфигурации порта – UCSRA, UCSRB, UCSRC.

UCSRA
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)
(только запись)
Работа в многопроцессорном режиме.

UCSRB
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 бит отправленны данных, в многопроцессорном режиме

UCSRC
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 Установка размера кадра

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

Проект в proteus

В рамках следующего урока будет рассмотрен .

Скачать файлы проекта

При написании статьи использовался datasheet на Atmega16.

23 комментария »

Alex_EXE | 02.04.2013 | AVR |

23 комментария на « AVR. 4. Работа с UART на примере Atmega16A»

  1. глеб пишет 11.03.2014 в 20:44 #

    Превосходно! это то, что я искал несколько дней 🙂 вы очень помогли

  2. Александр пишет 02.10.2014 в 14:49 #

    Очень познавательно. Побольше бы таких статей.

  3. vv8 пишет 20.01.2015 в 20:03 #

    Спасибо за отличное изложение!

  4. Multidos пишет 13.02.2015 в 23:12 #

    Отлично разжевано, спасибо, наконец понял что к чему +1

  5. Ваха пишет 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);
    Пробовал в протеусе — скорость правильная, как и должно по документации, а на практике совсем другая скорость. в чем дело?

  6. Ваха пишет 04.03.2015 в 13:34 #

    Добрый день всем! Разобрался, нужно было выставить fuse при прошивке в программаторе. Я считал, что если в программе указываю #define F_CPU 8M, то МК работает на этой частоте. Всем удачи!

  7. kitkit пишет 09.06.2015 в 03:00 #

    жаль, что автор забил на идею создания подробных уроков…

  8. Николай пишет 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)
    и так далее, все, что касается регистров МК такая же ошибка. Помогите, пожалуйста, что не так.

  9. Alex_EXE пишет 08.01.2016 в 23:30 #

    Среда разработки AtmelStudio?
    Библиотеки подключены? Пути верны?

  10. I пишет 20.03.2016 в 21:18 #

    Спасибо автору. Благодаря примеру нашел ошибки в своем коде.

    😉

  11. sardex пишет 26.03.2016 в 21:27 #

    Ни че тут не понял. Но когда поумнею начну писать более адекватные коменты. Автор пиши есчЁ (хотя эта фраза помоему уже заезжена, но все равно ПИШИ).

  12. Дмитрий пишет 03.04.2016 в 20:37 #

    Черт побери, 10 минут думал над строкой
    send_Uart(temp/10+’0′);

    пример, когда наглядность становится врагом понятности))

  13. Максим пишет 01.11.2016 в 01:22 #

    Добрый день.
    Всё получилось в «Proteus» но программа не работает с реальным микроконтроллером. У меня: atmega16, PL2303HX и Terminal 20.

  14. Alex_EXE пишет 05.11.2016 в 16:36 #

    Замкните выводы Rx Tx PL2303HX — проверьте его работу.
    Проверьте работу МК на других более простых примерах.
    Проверьте настройки фьюзов.

  15. Владимир пишет 18.11.2017 в 23:01 #

    Здравствуйте, в Си почти что ламер. Почему функция send_Uart(13); дает переход на строку ниже? Почему 13 а не 25 или 18 например?

  16. Alex_EXE пишет 19.11.2017 в 16:54 #

    13 (0Ch) — это ASCII код символа переноса строки, см ASCI кодировку. Ещё примеры: 9 (09h) код символа табуляции, 48 (30h) код символа 0, 255 (FFh) код символа я.

  17. Андрей пишет 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 (я использую данный микроконтроллер в своих начинающих опытах) ?

    Заранее Вам благодарен!

  18. Андрей пишет 27.01.2019 в 15:23 #

    Пардон, ошибся, у Вас как раз в статье Atmega16A, но суть вопроса от этого не меняется, так как в datasheet на данный МК частота по умолчанию также 1 МГц

  19. Alex_EXE пишет 29.01.2019 в 03:37 #

    Помоему там стоит по умолчанию 8МГц. Или во время прошивки данные фьюзы поправил.
    Редко и давно работал с Atmel, уже точно не помню.

    В коде 9 строка F_CPU 8000000UL стоит определение частоты подключенного/настроенного осцилятора для переменных delay, т.е. используемый МК у меня был настроен на 8МГц.

  20. Георгий пишет 24.05.2019 в 21:13 #

    А как быть с тем, что строка
    «38. UCSRC=0b10000110; // 8-битовая посылка» нагло пишет еще и в UBRRnH (параллельно-одновременно)?
    И симуляторе Студии Mega32 и Mega48 это видно.
    И что там будет со скоростью?

  21. denya241 пишет 02.04.2020 в 08:49 #

    Георгий понимаю что не актуально уже но в Атмел 7 даже не видно что пишет в UCSRC но если в H то видно. Где-то уже жаловались на эту проблемку просто программа так отрабатывает. Просто у них один адресс и при 7бите в 1 все автоматом уходит в С.

  22. Александр пишет 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));
    }

    если не трудно объясните,что нужно дописать,
    или скиньте ссылку на эту тему.

  23. YURI пишет 26.12.2022 в 21:07 #

    как передать в USART два байта для двух разных переменных

Комментарии RSS

Оставьте отзыв