Arduino по-взрослому: Си и иже с ним. Лекция 4. Работа с UART

Интерфейс взаимодействия UART, двоичное, десятеричное, шестнадцатеричное преставления чисел

Содержание

  1. Теория
  2. Практика

Теоретическая часть лекции

Теоретическая часть отлично изложена в исходной видеолекции:

Ссылки, приведенные в видео:

Практика на языке Си

Начнем, как и в исходной лекции, с инициализации модуля 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 и % b — дробная часть от деления числа a на число b.

И самая непонятная на первый взгляд процедура — UART_send_string(). На вход она получает строку, от которой ей требуется только адрес первого символа. Так повелось, что в конце каждой строки должен обязательно стоять байт 0х00, обозначающий конец строки. Цикл while (пока (условие) делать { команды; }) отправляет все байты по очереди, пока не дойдет до последнего — нулевого.

 

Думаю, для одной лекции этого более, чем достаточно. Я искренне надеюсь, что эта статья тебе понравилась и была полезной. До новых встреч! До новой дискриминации языка Си в лекциях! (шутка)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*

code