ARM

Путь джедая: Руководство для постигающих силу программирвания микроконтроллеров. Начало.

 

Путь джедая: Руководство для постигающих силу программирвания микроконтроллеров.

Джедай стремится к самосовершенствованию через познание и тренировки.

Любое программирование начинается с выбора среды разработки, тут самое главное понять почему нужно выбрать именно эту среду, а не другую, выбрав правильную среду разработки и систему сборки вы экономите время, экономя время, влечёт к увеличению вашей производительности труда, а значит тем самым вы добьётесь решения задачи и результата раньше, чем есть с той средой которая могла быть в неправильном выборе пути для программирования, оцените ваше восприятие кода в этой среде, возможности стилистического оформления и масштабирования среды. Не бойтесь переучиться на что-то более лучшее. Как только, вы начинаете думать и воспринимать новую информацию в контесте да зачем, если есть легче и проще, но что то более сложное даст результат выше, вы тем самым настраиваете себя, на то что вы не хотите развиваться, как только вы перестаете развиваться вы не конкурентно способны и весь мир на шаг впереди вас, вы больше никому не нужны. Развитие это шаг вперед, развивайтесь.

И так Qt Creator + qbs – мой путь. Почему Qt Creator + qbs вы можете подробно узнать по это ссылке, а по этой ссылке можно сравнить производительность qbs с другими системами сборки, если Вам нравится cmake, присмотритесь так же к Ninja, ранее я уже делал описание некоторых весомых причин использовать qbs и уже сейчас вы можете начать этим пользоваться.

Попробуем еще сделать работу с Qt Creator лучше, и так: хороший код, как ты не крути, должен быть документирован, когда у вас за год более 3-4 проектов, сделать дополнение в старый проект становится головной болью любого программиста, тут даже не помогает “самодокументированный код” когда семантика и описание кода уже включает описательные части его работы. Для документирования кода я рекомендую использовать следующий плагин qtcreator-doxygen. Скачать данный плагин, можно по этой ссылке.

Вам необходимо скачать и скопировать libDoxygen-0.4.7-qtc4.8.x-x86_64.so в Qt/Tools/QtCreator/lib/qtcreator/plugins/, далее включаем данный плагин в Qt creator для работы, а так же не забудте установить doxygen на вашу платформу.

Далее можно использовать комментирование файлов, в меню появилось поле для автоматического добавления комментариев. Про doxygen написано уже много статей, лично мне понравились следующее, которые дают основные навыки работы с doxygen:

      1. Документируем код эффективно при помощи Doxygen
      2. Оформление документации в Doxygen
      3. Построение диаграмм и графов в Doxygen

Следующий важный плагин, он кстати затрагивает предыдущий плагин doxygen описанный мной, SpellChecker-Plugin, который можно скачать по этой ссылке, второй плагин помогает нам писать грамотно, лично для меня писать грамотно сразу проблема, из-за моей невнимательности, настройка SpellChecker-Plugin ничем не отличается от настройки qtcreator-doxygen. Кроме самого плагина вам необходимо установить Hunspell, настроить путь до директории с Hunspell и выбрать нужный словарь, например русский или английский словарь.

И еще один важный момент который поможет сделать ваш код более привлекательным, со стороны его просмотра, конечно это форматирование кода и тут есть встроенные инструменты для форматирования кода, а это artistic style (astyle), clang format, uncrustify. Если у вас этих программ нет для форматирования кода тогда самое время начать устанавливать их.

И так пару слов снова про qbs, qbs позволяет вам группировать файлы согласно их функциональным принадлежностям, добавлять файлы достаточно легко, и просто и это не занимает много времени, например в других средах описанных мною добавление файлов, честно скажу, совсем неприятная процедура, особенно, когда проект пишется распределённо между программистами.

	Group {
		name: "HAL"
		prefix: HAL
		files: ["/Src/*.c", "/Inc/*.h", "/Inc/Legacy/*.h"]
		excludeFiles: ["/Src/stm32f7xx_hal_timebase_rtc_alarm_template.c", "/Src/stm32f7xx_hal_timebase_rtc_wakeup_template.c", "/Src/stm32f7xx_hal_timebase_tim_template.c"]
	}

И еще один момент, на котором я хотел бы акцентировать ваше внимание, это правила. Правила очень интересная вещь которая позволяет выполнять ваши команды запросов. Например иногда бывает, что вам необходимо часть информации предварительно загрузить на флеш память, доступ к которой возможен только через swd/jtag. Тогда, правило qbs может предварительно загрузить его, выполнив соответствующую команду openocd, так же можно прописать сохранение артефактов для тестирования на ci сервере, либо открыть дополнительные программы не прибегая к сторонним инструментам и средствам, это очень хорошая вещь, правила, дают значительные преимущества qbs относительно других систем сборки и сред разработки.

	Rule {
		id: binDebugFrmw
		condition: qbs.buildVariant === "debug"
		inputs: ["application"]

		Artifact {
			fileTags: ["bin"]
			filePath: input.baseDir + "/" + input.baseName + ".bin"
		}

		prepare: {

			var objCopyPath = "arm-none-eabi-objcopy"

			var argsConv = ["-O", "binary", input.filePath, output.filePath]
			var cmd = new Command(objCopyPath, argsConv)
			cmd.description = "converting to BIN: " + FileInfo.fileName(
						input.filePath) + " -> " + input.baseName + ".bin"

			//Запись в nor память по qspi
			var argsFlashingQspi = ["-f", "board/stm32f746g-disco.cfg", "-c", "init", "-c", "reset init", "-c", "flash write_bank 1 "
									+ output.filePath + " 0", "-c", "reset", "-c", "shutdown"]

			var cmdFlashQspi = new Command("openocd", argsFlashingQspi)
			cmdFlashQspi.description = "Wrtie to the NOR QSPI"

			//Запись во внутреннюю память
			var argsFlashingInternalFlash = ["-f", "board/stm32f746g-disco.cfg", "-c", "init", "-c", "reset init", "-c", "flash write_image erase "
											 + input.filePath, "-c", "reset", "-c", "shutdown"]

			var cmdFlashInternalFlash = new Command("openocd",
													argsFlashingInternalFlash)
			cmdFlashInternalFlash.description = "Wrtie to the internal flash"

			//Открытие openocd
			var cmdOpenOcd = new Command("terminator",
										 "-me terminator -me \'openocd -f board/stm32f746g-disco.cfg -c \"init\" -c \"reset init\"\' &")

			cmdOpenOcd.description = "Opening openocd"

			//Открытие Tracealyzer
			var cmdOpenTracealyzer = new Command("terminator",
												 "-me /home/user/jedi_way_mcu/Tracealyzer-4.2.11-linux64/launch-tz.sh &")
			cmdOpenTracealyzer.description = "Opening Tracealyzer"

			return [cmd, cmdFlashQspi, cmdFlashInternalFlash, cmdOpenOcd, cmdOpenTracealyzer]
		}
	}

Самая важная часть при написании под микроконтроллеры rtos, baremetal это конечно отладка. Во многих статьях и книгах отладка занимает самое последнее место, и я считаю, что это не правильно, после знакомства со средой необходимо сразу начинать с инструментов отладки, не бывает чтобы программа сразу компилировалась либо сразу корректно работала, и тут мы всегда начинаем отладку, и так хочется напомнить важные законы Мёрфи:

Теория ошибок

Ошибки так же неисчерпаемы, как и атом.

Аксиома 
В любой программе есть ошибки.

Закон пропорциональности 
Чем более программа необходима, тем больше в ней ошибок.

Следствие 
Ошибок не содержит лишь совершенно ненужная программа.

Фундаментальный закон теории ошибок. На ошибках учатся.

Следствие 1 
Программист, написавший программу, становится ученым.

Следствие 2
Чем больше программист делает ошибок, тем быстрее он делается ученым.

Следствие 3
Крупный ученый-программист никогда не пишет правильные программы.

Указание начинающему программисту
Если вы с первого раза сумели написать программу, в которой транслятор не обнаружил ни одной ошибки, сообщите об этом системному программисту. Он исправит ошибки в трансляторе.

Первый инструмент про который я бы хотел рассказать, это Tracealyzer. Tracealyzer – это лучшее решение на сегодняшний день для трассировки rtos. Вот вам небольшое видео которое показывает основные аспекты данной программы.

Данный инструмент поддерживает множество различных rtos, но я остановлюсь на freertos, как наиболее распространенной rtos в мире, по данным статистики.

И так приступим к добавлению Tracealyzer, он же TraceRecorder для FreeRTOS. TraceRecorder может работать как в потоковом режиме, так и в режиме записи. TraceRecorder поддерживает большое количество интерфейсов через которые можно получать отладочную информацию для Tracealyzer. Добавляем файлы в проект:

	property string TraceRecorder: path
								   + "/../../../Tracealyzer-4.2.11-linux64/FreeRTOS/TraceRecorder"

	Group {
		name: "TraceRecorder"
		prefix: TraceRecorder
		files: ["/*.c", "/streamports/ARM_ITM/*.c", "/streamports/ARM_ITM/include/*.h", "/include/*.h", "/config/*.h"]
	}

TraceRecorder, так же как FreeRTOS является конфигурированной вещью, по этому предварительно еще нужно сконфигурировать, файл для конфигурации trcConfig.h, конфигурация является простой, пояснения по конфигурации есть в файл trcConfig.h, базовые поля для конфигурации: TRC_CFG_HARDWARE_PORT, TRC_CFG_RECORDER_MODE, TRC_CFG_FREERTOS_VERSION.

А так же для трассировки необходимо вызвать vTraceEnable(TRC_START) перед стартом работы диспетчера задач freertos.

  vTraceEnable(TRC_START);

  /* Start scheduler */
  osKernelStart();
  
  /* We should never get here as control is now taken by the scheduler */

И да вы конечно можете изменять информацию для вывода через файл trcStreamingPort.c, например для itm_write_32, можно настроить порт itm, а их как вы знаете не один (речь идет про ITM ARM, вот хорошая статья недавно вышла про механизм ITM и как его настроить для Qt Creator – Перенаправляем printf() из STM32 в консоль Qt Creator).

static void itm_write_32(uint32_t data)
{
    if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) &&      /* ITM enabled */
            ((ITM->TER & 1UL) != 0UL)) {                     /* ITM Port #0 enabled */
        while (ITM->PORT[0].u32 == 0UL);
        ITM->PORT[0].u32 = data;
    }
}

/* This is assumed to execute from within the recorder, with interrupts disabled */
int32_t itm_write(void *ptrData, uint32_t size, int32_t *ptrBytesWritten)
{
	uint32_t bytesWritten = 0;
    uint32_t *ptr32 = (uint32_t *)ptrData;

	if (size % 4 != 0) return -2;

    while (bytesWritten < size) {
		itm_write_32(*ptr32);
		ptr32++;
		bytesWritten += 4;
	}

	*ptrBytesWritten = bytesWritten;

	return 0;
}

Лично я еще кроме ARM ITM использую для вывода логирования streamports/TCPIP и streamports/USB_CDC, для streamports/TCPIP нужен LwIP (как создать базовый проект для lwip c stm32f746g-disco можно почитать перейдя по этой ссылке).

Следующее, что я бы хотел сказать, если у вас не RTOS, а baremetal лучший способ это конечно ARM ITM, для вывода информации, ненужно использовать для этого отдельный uart, продублирую статью “Перенаправляем printf() из STM32 в консоль Qt Creator”, автор очень подробно описал как это делать, конечно uart старый проверенный способ, но он просто не подходит для автоматизированного тестирования, и для интеграции проекта с ci, лично по моему мнению, его самый главный минус это скорость, во вторых написание своих велосипедов для интеграций.

Что делать если есть любовь к вечно молодому uart, у меня есть для этого вам что сказать. У вас, как и у меня наверное много проектов, и uart-ом нужно пользоваться часто, тут я бы посоветовал, используйте xprintf библиотеку от Чена, вот ссылка на нее. Вам нужно всего лишь передать ваши функции для чтения и записи в xdev_out и xdev_in соответственно. И у вас есть фунуия xprintf, аналогично функция printf.

/*------------------------------------------------------------------------*/
/* Universal string handler for user console interface  (C)ChaN, 2011     */
/*------------------------------------------------------------------------*/

#ifndef _STRFUNC
#define _STRFUNC

#ifdef __cplusplus
extern "C" {
#endif

#define _USE_XFUNC_OUT	1	/* 1: Use output functions */
#define	_CR_CRLF		1	/* 1: Convert \n ==> \r\n in the output char */

#define _USE_XFUNC_IN	1	/* 1: Use input function */
#define	_LINE_ECHO		1	/* 1: Echo back input chars in xgets function */


#if _USE_XFUNC_OUT
#define xdev_out(func) xfunc_out = (void(*)(unsigned char))(func)
extern void (*xfunc_out)(unsigned char);
void xputc (char c);
void xputs (const char* str);
void xfputs (void (*func)(unsigned char), const char* str);
void xprintf (const char* fmt, ...);
void xsprintf (char* buff, const char* fmt, ...);
void xfprintf (void (*func)(unsigned char), const char*	fmt, ...);
void put_dump (const void* buff, unsigned long addr, int len, int width);
#define DW_CHAR		sizeof(char)
#define DW_SHORT	sizeof(short)
#define DW_LONG		sizeof(long)
#endif

#if _USE_XFUNC_IN
#define xdev_in(func) xfunc_in = (unsigned char(*)(void))(func)
extern unsigned char (*xfunc_in)(void);
int xgets (char* buff, int len);
int xfgets (unsigned char (*func)(void), char* buff, int len);
int xatoi (char** str, long* res);
#endif

#ifdef __cplusplus
}
#endif

#endif

Так же вывод printf можно определить в syscalls, но если логирование вашей системы должны быть не сплошной простыней, а каждый функционал в отдельном окне, тогда самый правильный выбор это ITM ARM, а так же если все-таки остановились на uart добавьте цветной вывод в консоль и используйте minicom с флагом color.

Я буду продолжать цикл статей как правильно программировать под микроконтроллеры, оставайся со мной на mcu.by.

Да пребудет с тобой Сила

Leave a Reply

Your email address will not be published. Required fields are marked *

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.