Содержание

Printf через UART

В статье Printf через ITM был рассмотрен способ вывода информации стандартного потока ввода/вывода stdio в ПК с помощью отладчика по интерфейсу ITM. Однако такой подход применим только для МК с ядром Cortex M3 и выше.

МК на Cortex M0 и M1, такие как 1986ВЕ1Т, 1986ВЕВ3Т, блока отладки ITM не имеют, поэтому вывести отладочную информацию с помощью отладчика нельзя. В данной статье мы рассмотрим, как реализовать ввод/вывод сообщений с помощью интерфейса UART. Получившийся пример программы с использованием ввода/вывода через UART для МК 1986ВЕ1Т можно скачать по ссылке.

Стандартные функции ввод/вывод

Ввод и вывод информации осуществляется через функции стандартной библиотеки. Прототипы рассматриваемых функций находятся в файле stdio.h. Эта библиотека содержит функции:

- printf() — для вывода информации;

- scanf() — для ввода информации.

Чтобы понять, как настроить ввод/вывод информации рассмотрим данные функции поподробнее. Структура построения стандартных функций ввода/вывода в Keil выглядит следующим образом:

Функции, которые доступны пользователю, находятся в самом верху данной структуры и называются высокоуровневыми (High-Level Functions). К таким функциям как раз и относятся printf() и scanf(). При их вызове обработка вводимой информации реализуется с помощью вызова низкоуровневых функций (Low-Level Functions), к ним относятся такие функции как fputc() и fgetc(). Данные функции в свою очередь вызывают системные функции, которые напрямую работают с периферией МК (System I/O Functions), например, с UART или CAN, перенаправляя на них поток данных. Наша задача будет реализовать эти системные функции для ввода/вывода информации по UART.

Для встраивания системных функций в структуру стандартных функций ввода/вывода Keil предоставляет шаблон специального файла Retarget.c. В нём цепочка вывода информации от fputc() перенаправляется в системную функцию sendchar(), а при вводе функция fgetc() ожидает информацию от getkey(). Две эти системные функции sendchar() и getkey() мы и опишем для работы с интерфейсом UART.

При желании можно не использовать Retarget.c от Keil, а самостоятельно объявить функции fputc() и fgetc(), в которых реализовать ввод/вывод по интересующему интерфейсу МК. Однако, в дополнение к функциям fputc() и fgetc() необходимо также объявить структуры:

struct __FILE { int handle; };
FILE __stdout;
FILE __stdin;

Если этого не сделать, то низкоуровневые библиотеки Си будут реализовывать механизм semihosting’a, и в начале программы будет вызвана инструкция BKPT, переводящая процессор в режим debug. Выполнение программы при этом останавливается.

Реализация функций для работы с UART

Прежде чем приступить к описанию системных функций sendchar() и getkey() необходимо настроить интерфейс UART. В SPL уже сделан специальный набор параметров для отладки с помощью UART в различных МК (некоторые стандартные примеры его успешно используют), который содержится в файле «MDR32F9Qx_config.h» и активируется с помощью макроопределения _USE_DEBUG_UART_. Этими параметрами мы воспользуемся при написании функции инициализации интерфейса UART.

После установки Pack'a файл «MDR32F9Qx_config.h» по умолчанию расположен по пути "Диск:\Keil\ARM\PACK\Keil\MDR1986BExx\1.5\Config". Если макроопределение _USE_DEBUG_UART_ не раскомментировать, то проект будет собираться с ошибками.

Листинг функции инициализации UART

void DebugUARTInit()
{
   UART_InitTypeDef UART_InitStructure;
   PORT_InitTypeDef PORT_InitStructure;
   uint32_t BaudRateStatus;
	
#if defined (USE_MDR1986VE3)
   RST_CLK_PCLKcmd((RST_CLK_PCLK_PORTD | RST_CLK_PCLK_UART2), ENABLE);
#elif defined (USE_MDR1986VE1T)
   RST_CLK_PCLKcmd((RST_CLK_PCLK_PORTC | RST_CLK_PCLK_UART1), ENABLE);
#elif defined (USE_MDR1986VE9x)
   RST_CLK_PCLKcmd((RST_CLK_PCLK_PORTF | RST_CLK_PCLK_UART2), ENABLE);
#elif defined (USE_MDR1901VC1T)
   RST_CLK_PCLKcmd((RST_CLK_PCLK_PORTF | RST_CLK_PCLK_UART3), ENABLE);
#endif

   /* Инициализация структуры параметров порта ввода/вывода */
   PORT_InitStructure.PORT_Pin = DEBUG_UART_PINS;
   PORT_InitStructure.PORT_FUNC = DEBUG_UART_PINS_FUNCTION;
   PORT_InitStructure.PORT_MODE = PORT_MODE_DIGITAL;
   PORT_InitStructure.PORT_SPEED = PORT_SPEED_MAXFAST;

   PORT_Init(DEBUG_UART_PORT, &PORT_InitStructure);

   UART_DeInit(DEBUG_UART);

   /* Инициализация структуры параметров UART */
   UART_InitStructure.UART_BaudRate            = DEBUG_BAUD_RATE;
   UART_InitStructure.UART_WordLength          = UART_WordLength8b;
   UART_InitStructure.UART_StopBits            = UART_StopBits1;
   UART_InitStructure.UART_Parity              = UART_Parity_No;
   UART_InitStructure.UART_FIFOMode            = UART_FIFO_ON;
   UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_RXE | UART_HardwareFlowControl_TXE;
											      
   /* ----- Инициализация UART ----- */
   UART_BRGInit(DEBUG_UART, UART_HCLKdiv1);	
   BaudRateStatus = UART_Init(DEBUG_UART, &UART_InitStructure);		
   if(BaudRateStatus == BaudRateValid){
	 UART_Cmd(DEBUG_UART,ENABLE);
     }
   else{
	 while(1);
       }
	
    printf("========System startup========\n\r");
    printf("Init Debug UART ... Ok\r\n");
}

Все основные параметры настройки UART берутся из файла «MDR32F9Qx_config.h», выбор МК определяется в файле «MDR32F9Qx_board.h». В случае успешной инициализации по UART будет отправлено соответствующее сообщение.

Теперь перейдём к описанию функции sendchar() и getkey() для работы с UART. Они получились небольшие, листинг ниже

int sendchar(int c)
{
   UART_SendData(DEBUG_UART, (uint8_t) c);
   // Ожидать, пока не закончится передача
   while (UART_GetFlagStatus(DEBUG_UART, UART_FLAG_TXFF) == SET);
   return (c);
}

int getkey ()
{
   // Ожидать, пока не начнётся передача
   while (UART_GetFlagStatus(DEBUG_UART, UART_FLAG_RXFE) == SET);
   return ( UART_ReceiveData(DEBUG_UART) );
}

Теперь осталось описать прототипы получившихся функций в заголовочном файле и подключить всё в наш проект. Пример использования printf() и scanf() для МК 1986ВЕ1Т со всеми описанными функциями можно скачать по ссылке в начале статьи.

Работа программы с иcпользованием терминала Putty