На примере микроконтроллера К1986ВЕ92QI (MDR32F9Q2I) и отладочной платы LDM-K1986BE92QI рассмотрим работу с UART, в соответствии с пунктом 27 спецификации на МК.
Будем выводить циклически строку в порт USB A (USB UART) функцией printf.
Также посланная из терминала строка выводится в него же. При посылке строки "led" выводится что команда найдена (имитация ввода команды).
/* Программа выводит в USB-UART циклически строку а также строку посланную в терминал на скорости 115 200 строку,
если строка "led" то выводит "led find" (имитация приема и распознавания команды
для платы через терминал */
#include "MDR32F9Q2I.h" // Макроопределения для K1986BE92QI (MDR32F9Q2I)
#include <stdio.h> // printf()
#include <stdarg.h> // va_list, va_arg..
#include <string.h> // strstr()
// Макроопределения чтобы можно было поменять используемый порт в одном месте
#define UART_RX 0 // Номер вывода RX UART
#define UART_TX 1 // Номер вывода TX UART
#define PORT_UART MDR_UART2 // Используемый UART
#define PORT_UART_GPIO MDR_PORTD // Используемый порт для выводов UART
#define UART_IRQHandler UART2_IRQHandler // Используемый обработчик прерываний
#define UART_IRQn UART2_IRQn // Используемое прерывание
#define PIN_LED 3 // Номер вывода светодиода
#define PORT_LED MDR_PORTB // Порт светодиода
void UART_IRQHandler(void); // Прототип обработчика прерывания (чтобы убрать предупреждение компилятора)
static void init_GPIO(void){ // Инициализация порта светодиода
MDR_RST_CLK->PER_CLOCK |= (1 << 22); // Разрешение тактирования порта B (п.14.7.8)
PORT_LED->OE |= (1 << PIN_LED); // Вход или выход. 0 - вход, 1 - выход (п.16.1.2)
PORT_LED->ANALOG |= (1 << PIN_LED); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
PORT_LED->PWR |= (1 << PIN_LED * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
}
static void init_CPU(void){ // Включаем тактирование от кварца 8 МГц. Умножители и делители не используем. То есть тактовая частота CPU 8 МГц.
MDR_RST_CLK->HS_CONTROL = 0x01; // Вкл. HSE внешний кварц (8 МГц) (п.14.7.3)
MDR_RST_CLK->CPU_CLOCK |= (2 << 0); // Источник для CPU_C1 = HSE (п.14.7.4)
MDR_RST_CLK->CPU_CLOCK |= (1 << 8); // Источник для HCLK = CPU_C3
}
static void init_UART(void){ // Инициализация UART на скорость 115 200 (при 8 000 000 тактовой частоты)
MDR_RST_CLK->PER_CLOCK |= (1 << 7); // Разрешение тактирования UART2 (п.14.7.8) PD0 - RX (вход МК), PD1 - TX (выход порта)
MDR_RST_CLK->PER_CLOCK |= (1 << 24); // Разрешение тактирования порта D (п.14.7.8)
PORT_UART_GPIO->FUNC |= (2 << UART_RX * 2); // Функция RX. 0 - порт (GPIO), 1 - основная, 2 - альтернативная, 3 - переопределенная (п.16.1.2) (п.4 т.2)
PORT_UART_GPIO->ANALOG |= (1 << UART_RX); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
PORT_UART_GPIO->PWR |= (3 << UART_TX * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
PORT_UART_GPIO->FUNC |= (2 << UART_TX * 2); // Функция TX. 0 - порт (GPIO), 1 - основная, 2 - альтернативная, 3 - переопределенная (п.16.1.2) (п.4 т.2)
PORT_UART_GPIO->ANALOG |= (1 << UART_TX); // Аналог или цифра. 0 - аналоговый, 1 - цифровой (п.16.1.4) (п.4 т.2)
PORT_UART_GPIO->PWR |= (3 << UART_TX * 2); // Передатчик. 0 - отключен, 1 - медленный, 2 - средний, 3 - быстрый (п.16.1.7)
MDR_RST_CLK->UART_CLOCK = (
(0 << 0u) // Установка делителя для UART1 = undefined (п.14.7.11)
|(0 << 8u) // Установка делителя для UART2 = 1
|(0 << 24u) // Разрешение тактовой частоты UART1
|(1 << 25u)); // Разрешение тактовой частоты UART2
// Параметры делителя при частоте = 8 000 000 Гц и скорости = 115200
PORT_UART->IBRD = 0x4; // Целая часть делителя скорости 8 000 000 / (16 * 115 200) = 4,34 (п.27.5.4)
PORT_UART->FBRD = 0x16; // Дробная часть делителя скорости (0,34 * 64 + 0,5) = 22 = 0x16 (п.27.5.4)
PORT_UART->LCR_H = (
(0 << 1) // Разрешение проверки четности
|(0 << 2) // Четность/нечетность (нет контроля)
|(0 << 3) // Стоп-бит = 1 бит
|(3 << 5) // Длина слова = 8 бит
|(0 << 7)); // Передача бита четности
// PORT_UART->LCR_H |= (1 << 4); // Разрешен буфер FIFO приемника и передатчика
// PORT_UART->IFLS &= ~(7 << 3); // Сбрасываем биты
// PORT_UART->IFLS |= ~(4 << 3); // Прерывание по заполнености буфера на 7/8
PORT_UART->CR = ((1 << 8) | (1 << 9) | 1); // Передачик и приемник разрешен, разрешение приемопередатчика UART2
NVIC_EnableIRQ(UART2_IRQn); // Разрашаем прерывания от UART
PORT_UART->IMSC |= (1 << 4); // Устанавливаем прерывание от приемника UARTRXINTR
}
static char printf_buf[260]; // Строка (буфер) для вывода
int printf(const char *format, ...){ // Реализация printf
char ch; // Считываемый символ из буфера для отправки в UART
int count = 0; // Счетчик (индекс символа в строке)
va_list arg_ptr; // Аргументы printf()
va_start(arg_ptr, format); // Заполняем аргументы
vsnprintf(printf_buf, sizeof(printf_buf) - 1, format, arg_ptr); // Помещаем итоговую строку в printf_buf
va_end(arg_ptr); // Завершаем обработку переменного числа параметров
ch = printf_buf[count]; // Берем первый символ для вывода в UART
while(ch){ // Перебираем символы в строке
if(ch == '\n'){ // Если перенос строки
PORT_UART->DR = '\r'; // Делаем возврат каретки (п.27.9.2)
while(PORT_UART->FR & (1 << 5)){} // Ждем готовности UART (освобождения буфера) (п.27.9.4)
}
PORT_UART->DR = ch; // Пишем байт в регистр данных (п.27.9.2)
while(PORT_UART->FR & (1 << 5)){} // Ждем готовности UART (освобождения буфера) (п.27.9.4)
ch = printf_buf[++count]; // Берем следующий символ
}
return 0; // Нужно вернуть что-то
}
static char buf_UART[80]; // Буфер (строка) для вычитавания данных из UART
static int count_UART = 0; // Счетчик (индекс) символа в строке
void UART_IRQHandler(void){ // Обработчик прерываний UART
char * pstr = NULL; // Указатель на строку поиска команды
while((PORT_UART->FR & (1 << 4)) == 0){ // Ждем готовности UART (заполнения буфера) (п.27.9.4)
char ch = (PORT_UART->DR & 0xFF); // Вычитываем символ данных из регистра UART, ограничиваем восьмью битами
buf_UART[count_UART] = ch; // Записываем в строку принятый символ
count_UART++; // Иникрементируем счетчик буфера
printf("%c", ch); // Реализуем отображение вводимых символов
if(ch == '\r' || ch == '\n'){ // Конец строки
buf_UART[count_UART] = 0; // Записываем в буфер символ окончания строки
count_UART = 0; // Сбрасываем счетчик (индекс) символа в строке
printf("read %s\n", buf_UART); // Выводим строку в терминал
pstr = strstr(buf_UART, "led\r"); // Ищем строку (имитация команды через терминал) с завершающим символом
if(pstr == buf_UART){ // Если найдено совпадение с начала строки (указатели совпадают)
printf("command led find \n"); // Выводим что команда найдена
}
}
}
}
int main (void){ // Главная функция
uint16_t count = 0; // Счетчик для мигания светодиодом
init_CPU(); // Инициализация тактирования
init_UART(); // Инициализация UART
init_GPIO(); // Инициализация порта со светодиодом.
while(1){ // Основной цикл
for(int i = 0; i < 500000; i++){} // Задержка
printf("Hi my dear friend!!! pi=%.2f, addr=%p\n", 3.14, (void *)&count_UART); // Выводим в UART строку
PORT_LED->RXTX = (MDR_PORTB->RXTX & 0xFFFFFFF0) | ((count & 0x01u) << PIN_LED); // Инвертировать значение бита светодиода. Не блокируем SWD порта JTAG A
count++; // Инкрементируем счетчик
}
}