Arduino по-взрослому: Си и иже с ним. Лекция 4. Работа с UART
Интерфейс взаимодействия UART, двоичное, десятеричное, шестнадцатеричное преставления чисел
Теоретическая часть лекции
Теоретическая часть отлично изложена в исходной видеолекции:
Ссылки, приведенные в видео:
- http://yadi.sk/d/v6Ab3fUTP_rb-Q — проект Algorithm Builder
- http://yadi.sk/i/X7lFKhASSTuTcg — замечательная книга Евстифеева (если ссылка не открывается, эту книгу можно скачать в разделе Библиотека моего сайта)
Практика на языке Си
Начнем, как и в исходной лекции, с инициализации модуля USART. Конечно, в Atmel Studio 6.2, которую я использую при разработке алгоритмов на языке Си, нет такого конфигуратора, как в AB, но даташит или книга Евстифеева с лихвой компенсируют этот недостаток.
#define F_CPU 16000000 //Программу желательно начинать с определения константы F_CPU - тактовой частоты #include <avr/io.h> //Библиотека ввода-вывода #include <util/delay.h> //Библиотека задержек int main(void) { //Инициализация UART /* 1. Инициализация начинается с определения скорости обмена * Подходящие значения регистров проще всего определить с помощью * таблиц, представленных в соответствующем разделе * технической документации конкретного процессора. * (Таблицы 24-4 - 24-7 для ATmega328) */ UBRR0 = 51; //19200 бод при тактовой частоте 16 МГц /* 2. Регистр USCR0A. Регистр управления и состояния * Флаги: * - RXC0 - байт информации получен и ожидает в буфере * - TXC0 - отправка байта завершена, модуль готов к дальнейшим действиям * - UDRE0 - буфер свободен * - FE0 - ошибка Frame error * - DOR0 - ошибка: не успеваем считывать приходящие данные * - UPE0 - ошибка проверки четности * Настройки: * - U2X0 - удвоение скорости обмена */ UCSR0A = (0<<RXC0)|(0<<TXC0)|(0<<UDRE0)|(0<<FE0)|(0<<DOR0)|(0<<UPE0)| (0<<U2X0); /* 3. Регистр USCR0B. Регистр управления и состояния * Настройки: * - RXCIE0 - разрешение прерыванияя по флагу RXC0 * - TXCIE0 - разрешение прерыванияя по флагу TXC0 * - UDRIE0 - разрешение прерыванияя по флагу UDRE0 * - RXEN0 - включение приемника * - TXEN0 - включение передатчика * - UCZ02 - см. описа-е регистра UCSR0C * - RXB80 - 8-й принятый бит, если осуществляется передача по 9 бит информации в посылке * - TXB80 - 8-й отправляемый бит, если осуществляется передача по 9 бит информации в посылке */ UCSR0B = (0<<RXCIE0)|(0<<TXCIE0)|(0<<UDRIE0)|(0<<RXEN0)|(1<<TXEN0)|(0<<UCSZ02)|(0<<RXB80)|(0<<TXB80); /* 4. Регистр USCR0С. Регистр управления и состояния * Настройки: * - UMSEL01, UMSEL00 - режим работы модуля * - UPM01, UPM00 - управление проверкой четности * - USBS0 - управление стоповыми битами * - UCSZ02, UCSZ01, UCSZ00 - управление размером посылки * - UCPOL0 - полярность тактовых импульсов */ UCSR0C = (0<<UMSEL01)|(0<<UMSEL00)|(0<<UPM01)|(0<<UPM00)|(0<<USBS0)|(1<<UCSZ01)|(1<<UCSZ00)|(0<<UCPOL0); while(1) { } }
Отмечу, что перечислять все биты регистра совершенно не обязательно. Можно просто вписать шестнадцатеричное значение или десятичное, например:
UBRR0 = 51; //Десятичное значение UCSR0A = 0x00; //Шестнадцатеричное значение UCSR0B = 0b00001000; //Двоичное значение UCSR0C = 0b00000110; //Двоичное значение ...
Отправка одного байта осуществляется крайне просто: мы записываем нужный байт в регистр UDR0 (регистр данных модуля USART), а модуль делает все остальное сам. Итого, алгоритм отправки одного байта выглядит так:
//Отправка одного байта while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = 25; //Записываем десятичное числовое значение 25 в буфер. Передача начнется автоматически
Оформим его в виду процедуры (подпрограммы), чтобы вызывать одной строкой.
//Отправка одного байта void UART_send(char data) { while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = data; //Записываем значение переменной data в буфер. Передача начнется автоматически } int main (void) { ... }
Теперь отправка байта в порт занимает всего одну строку.
int main (void) { ... UART_send(0xF0); //Отправка шестандцатеричного значения F0 ... }
Теперь отправим число и строку. Если у тебя нет желания разбираться, как работают эти алгоритмы, просто скопируй следующие процедуры в свою программу перед объявлением основной процедуры main:
//Отправка положительного целого четырехзначного десятичного числа void UART_send_int(int x) { while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = (x / 1000) + 0x30; //Находим первую цифру (целая часть от деления на 1000), прибавляем код символа "0" в кодовой таблице ASCII и записываем в буфер. Передача начнется автоматически while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = ((x / 100) % 10) + 0x30; //Находим первую цифру (остаток от деления [целой части от деления на 100] на 10), прибавляем код символа "0" в кодовой таблице ASCII и записываем в буфер. Передача начнется автоматически while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = ((x / 10) % 10) + 0x30; //Находим первую цифру (остаток от деления [целой части от деления на 10] на 10), прибавляем код символа "0" в кодовой таблице ASCII и записываем в буфер. Передача начнется автоматически while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = (x % 10) + 0x30; //Находим первую цифру (остаток от деления на 10), прибавляем код символа "0" в кодовой таблице ASCII и записываем в буфер. Передача начнется автоматически } //Отправка команды "Возврат каретки" (Перенос строки) void UART_send_BK(void) { while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = 0x0D; while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = 0x0A; } void UART_send_string(const char *data) { char c; //Заводим временную переменную типа Байт (char) while(( c = *data++ )) { //Считываем каждый байт, пока он не равен нулю while(!(UCSR0A & (1<<UDRE0))) {}; //Ожидаем, пока освободится буфер UDR0 = c; //И отправляем его } }
Теперь ты можешь использовать их в своей программе так:
UART_send_int(1234); //Отправка числа 1234 в виде строки UART_send_BK(); //Отправка команды переноса строки (Enter) UART_send_string("Hello World"); //Отправка строки Hello World UART_send_BK(); //Отправка команды переноса строки (Enter)
Теперь подробнее о принципе работы этих процедур.
Для начала, самая простая — UART_send_BK(). Суть этой процедуры в том, что она отправляет два байта: 0x0D и 0x0A, которые являются служебными в стандартной таблице символов ASCII и отвечают за перенос курсора на новую строку.
Следующая по сложности процедура — UART_send_int(). Суть ее работы основана на том, что арабские цифры в стандартной таблице символов ASCII расположены друг за другом, начиная с адреса 0x30. То есть, если «откусывать» от числа по одной цифре и прибавлять ее к адресу 0x30, мы получим адрес нужной цифры в стандартной таблице символов ASCII. Остается лишь отправить его. А с выделением цифр справятся два стандартных оператора языка Си: a / b — целая часть от деления числа a на число b и a % b — дробная часть от деления числа a на число b.
И самая непонятная на первый взгляд процедура — UART_send_string(). На вход она получает строку, от которой ей требуется только адрес первого символа. Так повелось, что в конце каждой строки должен обязательно стоять байт 0х00, обозначающий конец строки. Цикл while (пока (условие) делать { команды; }) отправляет все байты по очереди, пока не дойдет до последнего — нулевого.
Думаю, для одной лекции этого более, чем достаточно. Я искренне надеюсь, что эта статья тебе понравилась и была полезной. До новых встреч! До новой дискриминации языка Си в лекциях! (шутка)