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;
}

Немного изменён алгоритм, теперь он выглядит так:
  1. поздоровались;
  2. проинициализировали порты, настроили вывод светодиода и сразу погасили его, настроили кнопку;
  3. настроили системный таймер для использования функции задержки;
  4. сообщили о завершении настройки;
  5. вывели приглашение нажать кнопку;
  6. ждем, пока кнопка не будет нажата;
  7. сообщили об обнаружении нажатия;
  8. зажгли светодиод и немного подождали;
  9. вывели предложение отпустить кнопку;
  10. ожидаем отпускания кнопки;
  11. сообщили о том, что отпускание зафиксировано;
  12. погасили светодиод и немного подождали;
  13. закольцовываем выполнения, переходим к пункту 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 вы получили ошибку:

А в окне Consose что-то вроде этого:
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 осуществляющая вывод всей строки сразу, что позволяет в разы повысить скорость вывода текста в отладочную консоль. Рекомендую вам ознакомиться с данным примером самостоятельно, а так же попробовать добавить указанную функцию в наш проект. Если у вас всё получится, то вы неплохо облегчите себе жизнь в дальнейшем.
  • 0
  • 13 сентября 2011, 09:49
  • angel5a
  • 1
Файлы в топике: blinky_printf.zip

Комментарии (9)

RSS свернуть / развернуть
Извините а при программировании STM32VLDISCOVERY такую фичу можно использовать, напрмер в STM32 TrueSTUDIO или CooCox Coide?
0
Пологаю что должно быть. Только не скажу где что ставить. возможно по дефолту есть. Попробуйте тот же Hello World запустить у себя и посмотреть в свойствах линкера.
0
если посмотрите библиотеку printf.с в CooCox то там будет пустое тело
void PrintChar(char c)
{
/* Send a char like:
while(Transfer not completed);
Transmit a char;
*/
}
но сначала надо запустить усарт… чтобы чегото получить в терминалке…
0
printf очень помогает в отладке программы, особенно если нет добротного программатора/отладчика(ULINK,JLink и т.д.).
При написании проги для LPC11C14 программатора не-было, но благо он шьется через COM-port.А отлаживаться? Вот здесь и использовал printf.
0
  • avatar
  • Zov
  • 13 сентября 2011, 10:56
а куда и как перенаправляли вывод то?
0
Пример для LPC11C14(и ему подобным): подключаем файл RETARGET.C
Его код:

#include <stdio.h>
#include <rt_misc.h>

#pragma import(__use_no_semihosting_swi)


extern int  schar(int ch);  /* in serial.c */

struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;

int fputc(int ch, FILE *f) {
  return (schar(ch));
}
void _ttywrch(int ch) {
  schar(ch);
}
void _sys_exit(int return_code) {
label:  goto label;  /* endless loop */
}

В основном файле (там где у вас тело main)добавляем функции:
/*	Function used for print statements */
int  schar(int ch){
	uint8_t chr = ch;
	UARTSend(&chr, 1);
	return 0;
}

/* Function returns 1st character from the received UARTBuffer */
int32_t gchar(void) {
	uint8_t temp;

	while ( UARTCount == 0 );
	
	LPC_UART->IER = IER_THRE | IER_RLS;			/* Disable RBR */	  
	temp = UARTBuffer[0];
	UARTCount = 0;
	LPC_UART->IER = IER_THRE | IER_RLS | IER_RBR;	/* Re-enable RBR */
	return (int32_t)temp;
}


И потом лепим где нам нужно.Прим.:
printf(" LPC_CAN->IF2_DA1  -%4X ",LPC_CAN->IF2_DA1);

Для отладки можно пользоваться любой терминальной программой.
0
  • avatar
  • Zov
  • 13 сентября 2011, 14:16
Спасибо
0
Всегда пожалуйста.Одним общим делом же занимаемся=)
0
  • avatar
  • Zov
  • 14 сентября 2011, 12:48
Ну некоторые при «общем деле» смотрят как на конкурента. мол «я ему сейчас подскажу, а он меня потом подсидит». Приходилось мне и с такими иметь дело. Но в прочем мне же лучше. Когда сам разбираешься лучше запоминается.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.