IAR, STM32: отладочный вывод текстовых сообщений Печать
Добавил(а) microsin   

При написании и отладки программ всегда важно иметь не только полноценный JTAG-отладчик, но и удобный вывод текстовых сообщений в отдельную отладочную консоль. Эта возможность позволяет ориентироваться в алгоритме выполнения программой нужных задач, не останавливая сам процесс выполнения. Т. е. программа работает, и при этом сообщает, что с ней происходит, и этими сообщениями управляет программист. Такой функционал обычно реализуется с помощью библиотечной функции printf, и различными возможностями перенаправления вывода среды разработки и сопутствующих библиотек. В этой статье будет рассмотрен отладочный вывод на платформе STM32 в среде программирования IAR 6.50.

[Вывод текстовых сообщений в окно Terminal I/O]

Semihosting. По умолчанию вывод сообщений от printf передается в окно Terminal IO через библиотеку семихостинга, это поведение библиотеки ввода/вывода настроено по умолчанию (диалог Options проекта -> General Options -> Library Configuration -> Library low-level interface implementation -> stdout/stderr -> Via semihosting).

IAR-STM32-options-debug-output-semihosting

Таким образом, самый простой вывод отладочных сообщений можно осуществить, если просто подключить к проекту заголовочный файл stdio.h, и в любом месте программы использовать функцию printf. Тогда если после запуска отладки Вы откроете пункт меню View -> Terminal I/O, то в окне сообщений Terminal I/O увидите выводимые функцией printf данные.

IAR-STM32-debug-output-Terminal-IO

Готовый проект с примером кода можно скачать по ссылке [3] (проект printf-Terminal-IO в архиве). Внимание: такой вывод работает только в среде разработки IAR. Если Вы попытаетесь запустить такой проект автономно, то он сразу зависнет на первом же вызове функции printf.

SWO. Для вывода сообщений printf можно использовать технологию SWO. Как утверждается [4], эта технология позволяет очень качественно и быстро выводить сообщения, не нагружая при этом ядро работающего микроконтроллера. Т. е. якобы в этом отношении другие способы вывода отладочных сообщений (USART, семихостинг) проигрывают SWO.

Для того, чтобы включить вывод сообщений printf через SWO, нужно это настроить в диалоге свойств проекта (General Options -> Library Configuration -> Library low-level interface implementation -> stdout/stderr -> Via SWO).

IAR-STM32-options-debug-output-SWO

Кроме того, необходимо установить утилиту SEGGER J-Link SWO Viewer [4], без её запуска у меня почему-то не появлялись сообщения в окне Terminal IO.

SEGGER-STM32-debug-output-SWO-Viewer

Вывод в SWO может нормально работать автономно, т. е. без запуска в сессии отладки IAR. Достаточно подключить отладчик J-Link, и запустить утилиту J-Link SWO Viewer. И конечно же, Ваш J-Link должен поддерживать SWO, и на контакт 13 коннектора 20-pin JTAG должен приходить сигнал SWO от микроконтроллера.

STM32-P407-JTAG-SWO-connection

Недостаток такого отладочного вывода - необходимо иметь подключенный отладчик J-Link, и нужно дополнительно устанавливать утилиту J-Link SWO Viewer [4]. Также по сравнению с USART недостаток в том, что передача данных однонаправленная, т. е. реализовать консоль управления не получится.

[Перенаправление printf в USART]

Этот вывод запускается немного сложнее, потому что необходимо добавить код для настройки UART, и переназначить некоторые библиотечные функции. Зато код может работать автономно, независимо от того, запустили ли Вы его в среде отладки IAR, или нет, и не нужно использовать специальное программное обеспечение. Нужен только компьютер с портом RS-232 (его можно организовать через переходник USB-COM), и обычный терминальный клиент наподобие putty или HyperTerminal.

Микроконтроллер STM32F407 имеет целых 4 последовательных порта USART. На макетной плате Olimex STM32-P407 два из этих портов USART (USART6 и USART3) представлены 2 стандартными коннекторами DB9 мама, с цоколевкой под "прямой" кабель RS232. Один коннектор промаркирован как RS232_1, другой как RS232_2.

STM32-P407-RS232-connectors

Для обмена данными с компьютером в любом направлении достаточно только 3 провода: земля GND, прием RX и передача TX (сигналы для квитирования обмена CTS и RTS использовать необязательно). Ниже представлено такое соединение, осуществляемое "прямым" кабелем мама-папа.

компьютер   кабель              кабель   STM32-P407
DB9 папа   DB9 мама            DB9 папа   DB9 мама
  GND -5->>-5------------------------5->>-5- GND
   TX -3->>-3------------------------3->>-3- RX
   RX -2->>-2------------------------2->>-2- TX

Интерфейс UART удобно использовать при отладке как текстовую консоль, в которую во время работы программы можно выводить текстовые сообщения о разных событиях, которые происходят в коде. Иногда делают и обработку приема на стороне микроконтроллера, тогда можно реализовать ответ программы на какие-нибудь команды, которые пользователь вводит в консоли (это для отладки используется реже).

Коннектор RS232_1 подключен к USART6 (порт PC6 TX, PG9 RX), а коннектор RS232_2 подключен к USART3 (порт PD8 TX, PD9 RX). Одновременно со статической памятью U15 K6R4016V1D-TC10 коннектор RS232_2 использовать нельзя, потому что что порты PD8 и PD9 одновременно используются под аппаратную шину адреса. Таким образом, под отладку удобнее задействовать коннектор RS232_1.

Совместно с IAR 6.50 поставляются примеры от IAR/STM, которые демонстрируют работу с USART. Открыть примеры можно со стартового экрана IAR -> EXAMPLE PROJECTS -> ST -> STM32F4xx -> CMSIS and STM32F4xx stdperiph lib 1.1.0 -> USART. Самый интересный пример - проект USART_Printf, который демонстрирует пользовательское переопределение функции fputc (putchar), чтобы функция printf выводила данные в последовательный порт. Эту возможность очень удобно использовать для вывода отладочных сообщений.

Еще интересные примеры работы с USART есть в примерах [2] от компании Olimex, которые она поставляет с отладочными платами STM32-P407. Упрощенный пример работы с USART можно скачать по ссылке [3] (проект printf-USART-IO в архиве).

Всю инициализацию USART делает функция STM_EVAL_COMInit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * @brief Функция конфигурирует COM-порт.
 * @param COM: указывает, какой COM-порт конфигурировать.
 * Этот параметр может принимать следующие значения:
 * @arg COM1 (==0)
 * @arg COM2 (==1)
 * @param USART_InitStruct: указатель на заполненную структуру
 * USART_InitTypeDef, которая содержит конфигурационную
 * информацию для указанного периферийного устройства USART.
 * @retval None (функция ничего не возвращает).
 */
void STM_EVAL_COMInit(COM_TypeDef COM, USART_InitTypeDef* USART_InitStruct)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /* Разрешить тактирование GPIO */
  RCC_AHB1PeriphClockCmd(COM_TX_PORT_CLK[COM] | COM_RX_PORT_CLK[COM], ENABLE);
  
  if (COM == COM1)
  {
    /* Разрешить тактирование UART */
    RCC_APB1PeriphClockCmd(COM_USART_CLK[COM], ENABLE);
  }
  
  /* Подключение PXx к USARTx_Tx*/
  GPIO_PinAFConfig(COM_TX_PORT[COM], COM_TX_PIN_SOURCE[COM], COM_TX_AF[COM]);
  
  /* Подключение PXx к USARTx_Rx*/
  GPIO_PinAFConfig(COM_RX_PORT[COM], COM_RX_PIN_SOURCE[COM], COM_RX_AF[COM]);
  
  /* Конфигурирование USART Tx как альтернативной функции порта GPIO */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  
  GPIO_InitStructure.GPIO_Pin = COM_TX_PIN[COM];
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(COM_TX_PORT[COM], &GPIO_InitStructure);
  
  /* Конфигурирование USART Rx как альтернативной функции порта GPIO */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = COM_RX_PIN[COM];
  GPIO_Init(COM_RX_PORT[COM], &GPIO_InitStructure);
  
  /* Конфигурация USART */
  USART_Init(COM_USART[COM], USART_InitStruct);
    
  /* Разрешить USART */
  USART_Cmd(COM_USART[COM], ENABLE);
}

Разберем этот код по порядку.

Строка 17, вызов функции RCC_AHB1PeriphClockCmd. Если для значения COM было передано COM1 (т. е. 0), то функция разрешит тактирование GPIOC и GPIOD (относится к коннектору RS232_1), а если COM2 (т. е. 1), то будет разрешено тактирование GPIOD (относится к коннектору RS232_2). Обратите внимание, что в функцию RCC_AHB1PeriphClockCmd передаются значения из массивов COM_TX_PORT_CLK и COM_RX_PORT_CLK. Таким образом, если Вы хотите использовать по выбору и СOM1, и COM2, то эти массивы должны быть проинициализированы корректными смещениями адреса до нужных портов GPIO. В примере от Olimex эти массивы содержат по 2 ячейки, поэтому можно использовать и COM1, и COM2 по Вашему выбору (я оставил по умолчанию COM1, что соответствует выбору коннектора RS232_1 на плате STM32-P407).

Строка 19, проверка используемого номера COM-порта. Эта проверка нужна потому, что периферия USART и ножки микроконтроллера могут быть разнесены на разные порты GPIOx, в зависимости от того, какой COM-порт выбран (COM1 или COM2).

Строки 26, 29, вызов функции GPIO_PinAFConfig. Здесь происходит подключение альтернативной функции к ножкам портов GPIO для сигналов передачи и приема.

Строки 31..43, здесь происходит конфигурирование портов GPIO как альтернативной функции для приема и передачи.

Строки 46, 49, Функции USART_Init и USART_Cmd конфигурируют и разрешают работу USART.

IAR-STM32-debug-output-USART

IAR-STM32-debug-output-putty

Для того, чтобы функция printf выводила данные на нужное устройство вывода (в нашем случае это порт USART), необходимо переопределить функцию __write (так сделано в примерах от Olimex [2] и в примере printf-USART-IO [3]), либо функцию fputc (так сделано в примере USART_Printf от IAR/ST). Для ввода нужно переопределить функции __read или fgetc. Вот как переопределяется функция __write для вывода через USART:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/*************************************************************************
* Функция __write, осуществляющая вывод данных из буфера.
*************************************************************************/
size_t __write(int Handle, const unsigned char *Buf, size_t Bufsize)
{
   size_t nChars = 0;
  
   for (/* нет инициализации цикла */; Bufsize > 0; --Bufsize)
   {
      /* Цикл, пока не завершится передача */
      while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TXE) == RESET);
      USART_SendData(EVAL_COM1, *Buf++);
      ++nChars;
   }
   return nChars;
}

А вот так переопределяется функция fputc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * @brief Перенаправление библиотечной C-функции printf на USART.
 */
int fputc(int ch, FILE *f)
{
  USART_SendData(EVAL_COM1, (uint8_t) ch);
  
  /* Цикл, пока не завершится передача */
  while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TC) == RESET)
  {}
  
  return ch;
}

[Ссылки]

1. Olimex STM32-P407.
2. 140808STM32F407.zip - документация по микроконтроллерам STM32F40x/41x, по плате Olimex STM32-P407, схема, примеры кода.
3. 140814STM32-IAR-printf-using-examples.zip - проекты IAR 6.50, в которых демонстрируется использование функции printf для вывода отладочных сообщений.
4. J-Link SWO Viewer.
5STM32: аббревиатуры и термины.