LPCXpresso Урок 6. Semihosting. Использование printf в отладке.
В продолжение курса для новичков рассмотрим возможность вывода текстовых сообщений из контроллера в окно отладчика.
Что такое Semihosting
Наверняка вы задавались вопросом, чем отличается Semihosting проект от обычного проекта, созданного нами во втором уроке, и какой именно вам следует выбрать.
Я не знаю как переводится данный термин, но суть его в том, что бы для стандартной библиотеки С использовать "стандартные" возможности контроллера.
Давайте рассмотрим классику. Первое приложение на С из любого учебника (Искренняя благодарность Керниган и Ритчи за этот пример):
#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
Эта программа выводит на консоль строку Hello World! Но не мало важно что это кросс платформенное приложение (если мне не изменяет память то именно оно являлось демонстрацией кроссплатформенности). Но как это приложение может быть запущено на контроллере? Ведь у нас попросту нет в контроллере консоли.
На самом деле консоль то у нас есть: отладочная консоль. Это то самое окошко Console которое у нас висело пустым грузом (ну, сообщения то в него выводятся, например, во время загрузки прошивки). И в это окошко и будет производится вывод при вызове функции printf (что это за функция и зачем она, можете почитать в любом учебнике по С).
И что бы у нас заработал данный пример, нам надо при создании проекта выбирать Semihosting C project.
Добавляем вывод текста в консоль отладчика
Давайте научим наш контроллер говорить с нами на языке понятном нам. К сожалению, это будет буржуйский язык, поскольку при использовании русского у вас могут возникать некоторые неприятные особенности в виде нечитаемых символов. Так фраза Здравствуй, Мир! будет выглядеть как:
\320\227\320\264\321\200\320\260\320\262\321\201\321\202\320\262\321\203\320\271, \320\234\320\270\321\200!
За основу снова берём наш проект мигания светодиодом (можно создать и новый, но дальше станет ясно почему я предлагаю такой вариант).
Добавим в main.c строку
#include <stdio.h>
И отредактируем код функции main:
int main(void) {
printf("Hello World!\n");
GPIOInit();
GPIOSetDir(LED_PORT, LED_BIT, 1);
GPIOSetValue(LED_PORT, LED_BIT, LED_OFF);
GPIOSetDir(BUTTON_PORT, BUTTON_BIT, 0);
SysTick_Config(SystemCoreClock / 1000); // настройка таймера на период 1мс
printf("Init complite\n");
while(1) {
printf("Wait for button press\n");
while(GPIOGetValue(BUTTON_PORT, BUTTON_BIT) != BUTTON_DOWN);
printf("Button down -> turn on led\n");
GPIOSetValue(LED_PORT, LED_BIT, LED_ON);
delay_ms(100);
printf("Wait for button release\n");
while(GPIOGetValue(BUTTON_PORT, BUTTON_BIT) != BUTTON_UP);
printf("Button up -> turn off led\n");
GPIOSetValue(LED_PORT, LED_BIT, LED_OFF);
delay_ms(100);
}
return 0;
}
Немного изменён алгоритм, теперь он выглядит так:
- поздоровались;
- проинициализировали порты, настроили вывод светодиода и сразу погасили его, настроили кнопку;
- настроили системный таймер для использования функции задержки;
- сообщили о завершении настройки;
- вывели приглашение нажать кнопку;
- ждем, пока кнопка не будет нажата;
- сообщили об обнаружении нажатия;
- зажгли светодиод и немного подождали;
- вывели предложение отпустить кнопку;
- ожидаем отпускания кнопки;
- сообщили о том, что отпускание зафиксировано;
- погасили светодиод и немного подождали;
- закольцовываем выполнения, переходим к пункту 5.
Но если мы просто запустим проект на компиляцию, то получим непонятное сообщение об ошибки сборки, и в консоли кучу ругани. Связано это с тем, что проект наш не является semihosting, мы то изначально создавали простой проект. И что теперь? создавать проект заново и переносить в него весь код? На самом деле в этом нет необходимости.
Нам надо попросить, что бы проект использовал соответствующую библиотеку стандартных функций языка С. Для этого в свойствах проекта в группе C/C++ Build выбираем Settings. В этом окне, на закладке Tool settings в группе MCU Linker выбираем пункт Target и далее в списке Use C library выбираем Redlib (semihost):
Тем самым мы сказали, что в качестве консоли мы будем использовать консоль отладчика.
Запуск
Запускаете проект в отладчике на выполнение и наблюдаете, как ваши действия комментируются контроллером в окне отладчика. Таким нехитрым способом у нас есть возможность следить за ходом выполнения программы без применения пошагового режима отладки.
Однако программа стала бешено "тормозить". Это связано с тем, что printf выводит сообщения посимвольно, и при выводе сообщения задействуется отладчик и шина USB, а они хоть и скоростные, но имеют "долгий отклик". Таким образом, ядро стопориться до тех пор, пока сообщение не будет передано, и только после этого выполнение продолжается. Если закомментировать функции вывода, то реакция на кнопку будет идти в разы быстрее. Проверьте это самостоятельно, так как это демонстрирует, что именно вывод замедляет работу примера, а не использование другой библиотеки C.
Так же крайне важно отметить, что для работы функций вывода, а соответственно и вашей программы, отладчик становится необходим. И не просто плата отладчика, но и вся среда. Если вы просто подадите питание на плату, то контроллер так же просто повиснет на выводе отладочного сообщения. Но если вызов printf производиться не будет (скажем если он находится в условие, которое не выполняется), то и зависать будет не на чем и программа будет выполняться как обычно. Пример такого кода я приведу при ознакомлении с АЦП.
Теперь к размеру кода. Из исходных 3544 байт наш проект вырос до 18820 байт. Но даже если мы удалим весь добавленный код, размер будет 7536 байт. Связано это с тем, что использовали мы библиотеку Redlib (semihost). Она просит под себя несколько больше ресурсов, чем изначальная Redlib (none). Но в целом это не большая плата за упрощение отладки.
Возврат исходной библиотеки
Если вы переживаете за количество байт кода в вашем приложении, то вы можете просто вернутся к использованию библиотеки Redlib (none) в настройках проекта и пересобрать приложение.
Возможные ошибки
Если в консоли вместо текста появляются числа, то, вероятно, вы используете русские символы для вывода сообщений. Измените текст сообщения на текст, написанный латиницей.
Если вдруг в окне Problems вы получили ошибку:
fclose.c:(.text.fclose+0x4c): undefined reference to `_Csyscall1'То вы, вероятно, выбрали не правильную библиотеку.
fclose.c:(.text.fclose+0x72): undefined reference to `_Csys_tmpnam_'
c:/nxp/lpcxpresso_3.5/tools/bin/../lib/gcc/arm-none-eabi/4.3.3/../../../../arm-none-eabi/lib/thumb2\libcr_c.a(remove.o): In function `remove':
remove.c:(.text.remove+0x6): undefined reference to `_Csyscall1'
collect2: ld returned 1 exit status
make: *** [blinky.axf] Error 1
Вместо заключения
Да, это привносит некоторые неудобства при использовании устройства при отладке, но одновременно с этим позволяет сократить в разы время, затрачиваемое на отладку кода. А отладочная информация в конечном устройстве обычно не нужна. Поэтому не всё так ужасно как может показаться на первый взгляд.
Подключайте на время отладки эти 16кБайт кода, они того стоят.
Добавлю, что в примерах есть проект LPCXpresso1343_consoleprint в котором представлена функция consoleprint осуществляющая вывод всей строки сразу, что позволяет в разы повысить скорость вывода текста в отладочную консоль. Рекомендую вам ознакомиться с данным примером самостоятельно, а так же попробовать добавить указанную функцию в наш проект. Если у вас всё получится, то вы неплохо облегчите себе жизнь в дальнейшем.
Файлы: blinky_printf.zip