вторник, 25 октября 2016 г.

Makefile для сборки проектов на STM32, v2

В процессе использования Makefile, описанного в данной заметке, стали видны некоторые неудобства в его использовании. А именно: вынесение некоторых переменных в отдельные файлы создаёт проблемы при переносе и клонировании проекта, необходимость явного указания как файлов исходного кода, так и получаемых из них объектных файлов создаёт обширное поле для ошибок, также явное задание правил компиляции каждого файла добавляет неразберихи и, опять же, возможности для ошибок, в проект.
Хотелось, чтобы после задания произвольного количества файлов исходного кода их компиляция в объектные файлы происходила без явного создания правил для каждого из них. Также желательно не использовать сторонние средства (внешние скриптовые языки, Perl, Python и проч.), а ограничиться возможностями языка Makefile.
В результате получился следующий файл:

# Выбор типа ядра контроллера
MCU = cortex-m4
# Адрес начала внутренней FLASH-памяти контроллера
LOADADDRESS = 0x08000000
# Использовать драйверы периферийных устройств, предоставленные ST.
# Компиляция для контроллеров серии STM32F373x
DEFINES = -DUSE_STDPERIPH_DRIVER -DSTM32F373xC

# Путь к каталогу драйверов периферии контроллера и вспомогательных библиотек
BASEDIRNAME = /c/STM32Cube/Repository/STM32Cube_FW_L1_V1.6.0/
# Скрипт линковщика, специфичный для контроллера
# Возможно, придётся забрать его в каталог проекта,
# и внести изменения (размер стека, объём внутренней
# FLASH- и RAM-памяти)
LDSCRIPT = $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/gcc/linker/STM32F373XC_FLASH.ld
# Путь к каталогу кросскомпилятора
GCCDIRNAME = /c/gcc-arm-none-eabi-5_3-2016q1-20160330-win32/bin/
# Префикс кросскомпилятора
PREFIX = arm-none-eabi-

# Файл конфигурации OpenOCD для программирования заданного контроллера
OPENOCDBOARD = stm32f3discovery.cfg
# Путь к каталогу OpenOCD
PROGRAMMERDIR = /c/openocd-0.9.0/
# Путь к исполняемому файлу OpenOCD, для разных платформ у него разные имена
PROGRAMMERBIN = $(PROGRAMMERDIR)bin-x64/openocd.exe
# Если программируем самодельную плату, то используется один файл конфигурации
# Для демонстрационной платы -- другой
ifdef OPENOCDINTERFACE
 OPENOCDCONFIG = -f $(PROGRAMMERDIR)scripts/interface/$(OPENOCDINTERFACE)
else ifdef OPENOCDBOARD
 OPENOCDCONFIG = -f $(PROGRAMMERDIR)scripts/board/$(OPENOCDBOARD)
else
 $(error "Not defined OPENOCDINTERFACE or OPENOCDBOARD")
endif
# Команды OpenOCD для загрузки файла в контроллер
OPENOCDLOAD = "program result.bin $(LOADADDRESS) verify"
OPENOCDPROGRAM =\
$(OPENOCDCONFIG)\
-c "init"\
-c "halt"\
-c $(OPENOCDLOAD)\
-c "reset run"\
-c "shutdown"
## To unlock chip use next commands after "halt":
# -c "flash protect 0 0 last off"\
# -c "shutdown"

# Для компиляции всех типов файлов и линковки используем gcc.exe
# Он сам разберётся, что вызывать для переданного файла
AS = $(GCCDIRNAME)$(PREFIX)gcc
CC = $(GCCDIRNAME)$(PREFIX)gcc
CXX = $(GCCDIRNAME)$(PREFIX)gcc
LD = $(GCCDIRNAME)$(PREFIX)gcc
# Получение листинга из объектного файла
OBJDUMP = $(GCCDIRNAME)$(PREFIX)objdump
# Преобразование между форматами бинарных файлов
OBJCOPY = $(GCCDIRNAME)$(PREFIX)objcopy

OFLAGS = -Os
CXXSTANDARD = -std=c++11
CSTANDARD = -std=c11

# Каталоги, в которых будут искаться заголовочные файлы
INCLUDES = $(shell pwd)/
INCLUDES += $(BASEDIRNAME)Drivers/CMSIS/Include/
INCLUDES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Include/
INCLUDES += $(BASEDIRNAME)Drivers/STM32F3xx_HAL_Driver/Inc/

# Флаги, общие для почти всех команд сборки проекта
COMMONFLAGS = -mcpu=$(MCU) -mthumb -MD $(INCLUDES:%=-I%) -c

# Флаги, специфичные для компиляции ассемблерных файлов
ASFLAGS += $(COMMONFLAGS)
# Любые файлы, переданные в переменную ASFILES, трактовать, как ассемблерные
ASFLAGS += -x assembler-with-cpp

# Флаги, специфичные для компиляции файлов C
CFLAGS += $(COMMONFLAGS)
CFLAGS += $(CSTANDARD)
CFLAGS += $(OFLAGS)
CFLAGS += $(DEFINES)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall
CFLAGS += -fno-exceptions -ffunction-sections -fdata-sections -Wextra -Winline -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -pedantic
CFLAGS += -ggdb3
# Любые файлы, переданные в переменную CFILES, трактовать как файлы C
CFLAGS += -x c

# Флаги, специфичные для файлов C++
CXXFLAGS += $(COMMONFLAGS)
CXXFLAGS += $(CXXSTANDARD)
CXXFLAGS += $(OFLAGS)
CXXFLAGS += $(DEFINES)
CXXFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CXXFLAGS += -Wall
CXXFLAGS += -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wextra -Winline -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -pedantic
CXXFLAGS += -ggdb3
# Любые файлы, переданные в переменную CXXFILES, трактовать как файлы C++
CXXFLAGS += -x c++

# Флаги, специфичные для линкера
LDFLAGS += -Wl,-Map=$(TARGET).map -Wl,--cref
LDFLAGS += -Wl,--start-group -lc_nano -lrdimon_nano -lm -Wl,--end-group
LDFLAGS += -Wl,--gc-sections
# Из переменной COMMONFLAGS забираем элементы с первого по второй
LDFLAGS += $(wordlist 1, 2, $(COMMONFLAGS))

OBJCOPYFLAGS = -O binary
OBJDUMPFLAGS += -x -S

# Объектные и промежуточные файлы сохраняются в этом каталоге, чтобы не засорять проект
OBJDIR = ./obj/
-include $(shell mkdir $(OBJDIR) 2> /dev/null) $(wildcard $(OBJDIR)\*)

# Из каталога с данным Makefile разбираем ассемблерные файлы,
# файлы C и C++ по соответствующим переменным
ASFILES = $(wildcard *.s *.S *.asm)
CFILES = $(wildcard *.c)
CXXFILES = $(wildcard *.cpp)
# Добавляем файлы в проект
ASFILES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/gcc/startup_stm32f373xc.s
CFILES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/system_stm32f3xx.c
CXXFILES +=
# Объектные файлы будут с именем соответствующих файлов исходников,
# с добавлением расширения .o
# Таким образом, на сей момент недопустимо наличие в проекте
# файлов с одинаковыми именами для каждого из типов (asm, C, C++).
# В дальнейшем надеюсь это поправить
ASOBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(ASFILES:%=%.o)))
COBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(CFILES:%=%.o)))
CXXOBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(CXXFILES:%=%.o)))

.PHONY : clean all program

# Из элементов переменной *FILES для компиляции выбирается файл,
# имя и расширение которого совпадают с одним из элементов
# переменной *OBJFILES с отброшенным расширением .o
# Если в переменной *FILES окажутся файлы с одинаковыми именами
# и расширениями, то компиляция пройдёт некорректно.
$(ASOBJFILES) : $(ASFILES)
 @echo "-> compiling $@ as ASM-file"
 $(AS) $(ASFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@

$(COBJFILES) : $(CFILES)
 @echo "-> compiling $@ as C-file"
 $(CC) $(CFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@
 $(OBJDUMP) $(OBJDUMPFLAGS) $@ > $(@:%.o=%.lst)

$(CXXOBJFILES) : $(CXXFILES)
 @echo "-> compiling $@ as C++-file"
 $(CXX) $(CXXFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@
 $(OBJDUMP) $(OBJDUMPFLAGS) $@ > $(@:%.o=%.lst)

# Сборка axf-файла из полученных объектных файлов
result.axf : $(ASOBJFILES) $(COBJFILES) $(CXXOBJFILES)
 @echo "-> linking"
 $(LD) -T$(LDSCRIPT) $^ $(LDFLAGS) -o $@

# Преобразование axf-файла в бинарный, и получение ассемблерного листинга
all : result.axf
 @echo "-> copying"
 $(OBJCOPY) $(OBJCOPYFLAGS) $^ result.bin
 $(OBJDUMP) $(OBJDUMPFLAGS) $^ > result.lst

# Заливка бинарного файла в контроллер
program :
 @echo "-> load firmware to controller"
 $(PROGRAMMERBIN) $(OPENOCDPROGRAM)

# Очистка проекта
clean :
 $(RM) -f *.axf *.map *.lst $(OBJDIR)*.d *.bin $(OBJDIR)*.o $(OBJDIR)*.lst


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

четверг, 14 августа 2014 г.

Две идеи рядом:
1. Сделать мышь для компьютера на акселерометре. В простейшем варианте будут проблемы с движением по негоризонтальным поверхностям, но, возможно, не сильно хитрой математикой + статистикой удастся обеспечить распознавание угла наклона;
2. Опять таки мышь, но в виде приложения для смартфона. Bluetooth там есть, камера с достаточным разрешением тоже, процессор, графический ускоритель -- фотографируй, рассчитывай, куда двинули корпус, отдавай в компьютер по Bluetooth-у. Нажатие кнопок, прокрутка -- сенсорный экран. Можно на него ещё и функцию трекбола навесить, чтобы корпус смарта не тягать, а по экрану пальцами, пальцами. А ещё в смартфонах бывает акселерометр (см. идею 1).

Заняться на досуге, что ли?

пятница, 30 августа 2013 г.

Makefile для проектов на контроллерах STM32

Проекты, если иное не указано явно, состоят из четырёх пользовательских файлов: main.cpp, main.hpp, interrupts.cpp и interrupts.hpp. Предполагается, что среда сборки настроена в соответствии с рекомендациями, приведёнными в предыдущей заметке.
Для обеспечения быстрой смены компилятора (при выходе новой версии) и/или программатора пути к их каталогам и некоторые команды вынесены в отдельные файлы, подключаемые в проект. Путь к компилятору находится в файле GccDir.mk, путь к программатору и его команды -- в Programmator.mk. Оба этих файла находятся в директории на уровень выше (../), что обеспечивает возможность централизованного перехода на новые версии компилятора/программатора для всех проектов сразу.

Makefile с путями к компилятору
# GccDir.mk
GCCDIRNAME = /d/bin/gcc-arm-none-eabi-4_8-2014q1-20140314-win32
#GCCDIRNAME = /d/bin/gcc-arm-none-eabi-4_8-2013q4-20131204-win32
#GCCDIRNAME = /d/bin/gcc-arm-none-eabi-4_7-2013q3-20130916-win32
#GCCDIRNAME = /d/bin/gcc-arm-none-eabi-4_7-2013q2-20130614-win32
#GCCDIRNAME = /d/bin/gcc-arm-none-eabi-4_7-2013q1-20130313-win32
# Вот сколько версий вышло с момента публикации

Makefile с путями и командами программаторов OpenOCD и ST-Link. OpenOCD успел обновиться до версии 0.8.0.
# Programmator.mk
# **********************************************************************************************
#                            FLASH PROGRAMMING
#
# Alternate make target for flash programming only
#
# OpenOCD is run in "batch" mode with a special configuration file and a script file containing
# the flash commands. When flash programming completes, OpenOCD terminates.
#
# Note that the script file of flash commands (script.ocd) is part of the project
#
# Programmers: Martin Thomas, Joseph M Dupre, James P Lynch
# **********************************************************************************************


# OpenOCD directory
OPENOCDDIR = $(GCCDIRNAME)/downloads/openocd-0.8.0
# OpenOCD binary
OPENOCD = $(OPENOCDDIR)/bin-x64/openocd-x64-0.8.0.exe
# OpenOCD config
OPENOCDCONFIG =\
-f $(OPENOCDDIR)/scripts/interface/stlink-v2.cfg\
-f $(OPENOCDDIR)/scripts/board/stm32f3discovery.cfg

ifeq ($(STARTFROMRAM), true)
LOADCOMMAND = "load_image $(TARGET)-$(MCU)-$(BINFILESUFFIX).bin $(STARTADDRESS) bin"
else
LOADCOMMAND = "program $(TARGET)-$(MCU)-$(BINFILESUFFIX).bin $(STARTADDRESS) verify"
endif

# OpenOCD command
OPENOCDCOMMAND =\
-c "init"\
-c "halt"\
-c $(LOADCOMMAND)\
-c "reset run"\
-c "shutdown"
## To unlock chip use next commands after "halt":
# -c "flash protect 0 0 last off"\
# -c "shutdown"

# **********************************************************************************************
#                                 ST-Link programmator
# **********************************************************************************************
STLINKDIR = "/c/Program Files (x86)/STMicroelectronics/STM32 ST-LINK Utility/ST-LINK Utility"
STLINK = $(STLINKDIR)/ST-LINK_CLI.exe
STLINKCOMMAND = -c SWD UR -Halt -P "$(TARGET)-$(MCU)-$(BINFILESUFFIX).bin" $(STARTADDRESS) -Rst -V

program:
 @echo "STARTADDRESS = $(STARTADDRESS)"
 # $(STLINK) $(STLINKCOMMAND)
 $(OPENOCD) $(OPENOCDCONFIG) $(OPENOCDCOMMAND)

Makefile проекта.
TARGET = main
# Использовать драйверы периферийных устройств, предоставленные ST
DEFS = -DUSE_STDPERIPH_DRIVER

# Адрес начала внутренней FLASH-памяти контроллера
STARTADDRESS = 0x08000000
BINFILESUFFIX = flash

# Выбор типа контроллера и названия демонстрационной платы
MCU = cortex-m4
CHIP = STM32F37x
BOARD = STM32373C-EVAL
# Подключаем файлы с путями и командами компилятора и программатора
include ../GccDir.mk
include ../Programmator.mk

# Путь к каталогу стандартный драйверов периферии, и вспомогательных библиотек
STM32BASE = $(GCCDIRNAME)/downloads/$(CHIP)_DSP_StdPeriph_Lib_V1.0.0/Libraries
# Путь к каталогу специфичных для демонстрационной платы определений и функций
STM32373CBASE = $(STM32BASE)/../Utilities/STM32_EVAL
# Пути поиска заголовочных файлов, и файлов исходных кодов
INCLUDES =\
-I.\
-I'$(STM32BASE)/$(CHIP)_StdPeriph_Driver/inc'\
-I'$(STM32BASE)/$(CHIP)_StdPeriph_Driver/src'\
-I'$(STM32BASE)/CMSIS/Device/ST/$(CHIP)/Include'\
-I'$(STM32BASE)/CMSIS/Device/ST/$(CHIP)/Source/Templates'\
-I'$(STM32BASE)/CMSIS/Include'\
-I'$(STM32BASE)/../Project/$(CHIP)_StdPeriph_Templates'\
-I'$(STM32373CBASE)/Common'\
-I'$(STM32373CBASE)/$(subst -,_,$(BOARD))'

# Программы, используемые при сборке проекта
PREFIX = arm-none-eabi-
AR = $(GCCDIRNAME)/bin/$(PREFIX)ar
CC = $(GCCDIRNAME)/bin/$(PREFIX)gcc
CXX = $(GCCDIRNAME)/bin/$(PREFIX)g++
AS = $(CC)
LD = $(CC)
OBJCOPY = $(GCCDIRNAME)/bin/$(PREFIX)objcopy
OBJDUMP = $(GCCDIRNAME)/bin/$(PREFIX)objdump

# Уровень оптимизации
OPTIMIZE = -Os

# Общие влаги компиляции
FLAGS = -mcpu=$(MCU) -mthumb
FLAGS += -MD
FLAGS += $(INCLUDES)
FLAGS += $(DEFS)

# Скрипт линковщика, специфичный для контроллера
# Возможно, придётся забрать его в каталог проекта,
# и внести изменения (размер стека, объём внутренней
# FLASH- и RAM-памяти)
LD_SCRIPT = $(STM32BASE)/../Project/$(CHIP)_StdPeriph_Templates/TrueSTUDIO/$(BOARD)/STM32_FLASH.ld

# Флаги, специфичные для компиляции ассемблерных файлов
AFLAGS = $(FLAGS)
AFLAGS  += -x assembler-with-cpp -c

# Флаги, специфичные для компиляции файлов C++
CXXFLAGS = $(FLAGS)
CXXFLAGS += $(OPTIMIZE)
CXXFLAGS += -g
CXXFLAGS += -fno-exceptions -fno-rtti
CXXFLAGS += -ffunction-sections -fdata-sections
CXXFLAGS += -fno-threadsafe-statics
CXXFLAGS += -funsigned-bitfields -fshort-enums
CXXFLAGS += -Wall -Wextra
CXXFLAGS += -Winline
CXXFLAGS += -Wpointer-arith -Wredundant-decls
CXXFLAGS += -Wshadow -Wcast-qual -Wcast-align -pedantic
CXXFLAGS        += -c -std=c++11

# Использовать библиотеку C-runtime,
# оптимизированную для применения в микроконтроллерах
USE_NANO = --specs=nano.specs
# Использовать "заглушки" для некоторых
# функций C-runtime, неиспользуемых в микроконтроллерах
USE_SEMIHOST = --specs=rdimon.specs

# Флаги линкера
LD_FLAGS = -mcpu=$(MCU)
LD_FLAGS += -mthumb
LD_FLAGS += -Wl,-Map="$(MAP)",--cref
LD_FLAGS += -Wl,--gc-sections
LD_FLAGS += -T$(LD_SCRIPT)
LD_FLAGS        += $(USE_NANO)
LD_FLAGS        += $(USE_SEMIHOST)
# Для использования в проекте функций, обращающихся к C- C++-runtime
# после -lrdimon добавить -lc и/или -lstdc++
LD_FLAGS        += -Wl,$(LINKPATH),--start-group -lrdimon -Wl,--end-group

# Флаги для objcopy, создание бинарного файла прошивки
CPFLAGS = --output-target=binary
# Флаги для objdump, создание листинга программы (ассемблерный код).
ODFLAGS = -h -S

# Чтобы не разводить свалку объектных файлов в корневом каталоге проекта, будем складывать их в отдельный каталог
OBJDIR = ./obj

# Начальная инициализация микроконтроллера
STM32STARTUPFILENAME = startup_$(CHIP)
STM32SYSTEMFILENAME = system_$(CHIP)
# Запуск микроконтроллера
STM32STARTUP = $(STM32BASE)/CMSIS/Device/ST/$(CHIP)/Source/Templates/gcc_ride7/$(STM32STARTUPFILENAME).s
STM32SYSTEM  = $(STM32BASE)/CMSIS/Device/ST/$(CHIP)/Source/Templates/$(STM32SYSTEMFILENAME).c

# Библиотека ввода-вывода (GPIO)
GPIOSTDFILENAME = $(CHIP)_gpio
GPIOSTD = $(STM32BASE)/$(CHIP)_StdPeriph_Driver/src/$(GPIOSTDFILENAME).c

# Библиотека управления тактированием микроконтроллера (RCC)
RCCSTDFILENAME = $(CHIP)_rcc
# RCC stdlib code
RCCSTD = $(STM32BASE)/$(CHIP)_StdPeriph_Driver/src/$(RCCSTDFILENAME).c

# Определения и функции, специфичные для демонстрационной платы
EVBOARDHARDWFILENAME = $(subst -,_,$(BOARD))
EVBOARDHARDW = $(STM32373CBASE)/$(subst -,_,$(BOARD))/$(EVBOARDHARDWFILENAME).c

# Обработчики прерываний
INTWORKFILENAME = interrupts
INTWORK = ./$(INTWORKFILENAME).cpp

# Создание map-файла
MAP = $(TARGET).map

# Пути к объектным файлам из корневого каталога проекта
STM32STARTUPOBJ = $(OBJDIR)/$(STM32STARTUPFILENAME).o
STM32SYSTEMOBJ = $(OBJDIR)/$(STM32SYSTEMFILENAME).o
GPIOSTDOBJ = $(OBJDIR)/$(GPIOSTDFILENAME).o
RCCSTDOBJ = $(OBJDIR)/$(RCCSTDFILENAME).o
EVBOARDHARDWOBJ = $(OBJDIR)/$(EVBOARDHARDWFILENAME).o
INTWORKOBJ = $(OBJDIR)/$(INTWORKFILENAME).o
TARGETOBJ = $(OBJDIR)/$(TARGET).o

# Объектные файлы, из которых будет собираться финальный образ
OBJECTS =\
$(STM32STARTUPOBJ) $(STM32SYSTEMOBJ) $(GPIOSTDOBJ)\
$(RCCSTDOBJ) $(EVBOARDHARDWOBJ) $(INTWORKOBJ)\
$(TARGETOBJ)

# Сборка финального образа программы, и её листинга
all: $(TARGET)-$(MCU).axf
 @echo "....copying"
 $(OBJCOPY) $(CPFLAGS) $^ $(TARGET)-$(MCU)-$(BINFILESUFFIX).bin
 $(OBJDUMP) $(ODFLAGS) $^ > $(TARGET)-$(MCU).lst

# Линковка промежуточного образа программы
$(TARGET)-$(MCU).axf: $(OBJECTS) $(LDSCRIPT)
 @echo "...linking"
 $(LD) $^ $(LD_FLAGS) -o $@

$(STM32STARTUPOBJ): $(STM32STARTUP)
 @echo "..assembling of $^"
 $(AS) $(AFLAGS) $< -o $@

$(STM32SYSTEMOBJ): $(STM32SYSTEM)
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

$(GPIOSTDOBJ): $(GPIOSTD)
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

$(RCCSTDOBJ): $(RCCSTD)
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

$(EVBOARDHARDWOBJ): $(EVBOARDHARDW)
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

$(INTWORKOBJ): $(INTWORK)
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

$(TARGETOBJ): $(TARGET).cpp
 @echo ".compiling of $^"
 $(CXX) $(CXXFLAGS) $< -o $@

clean:
 rm -f $(OBJECTS) $(TARGET)*.axf *.map *.lst $(OBJDIR)/*.d *.bin *.o

# Создаём каталог для объектных файлов, если уже создан -- ошибки не будет
-include $(shell mkdir obj 2> /dev/null) $(wildcard obj\*)


среда, 7 августа 2013 г.

Настройка среды сборки для контроллеров ARM (AT91SAM7Sxxx и STM32F3xx)

1. Конфигурация системы
Развёртывание и конфигурирование среды сборки производилось в операционной системе Windows 7 x86-64. Использовались демонстрационная плата AT91SAM7S-EK с JTAG-отладчиком J-Link-ARM, шедшим с ней в комплекте, и плата STM32373C-EVAL.
2. Переключение Windows в тестовый режим
Ранее на этом же компьютере был установлен драйвер ввода-вывода dlportio, что потребовало переключения Windows в тестовый режим, в котором отсутствует проверка цифровой подписи драйверов. Переключение между режимами описано в этой статье, там же приведена и ссылка на утилиту, с помощью которой производится переключение.
3. Компилятор gcc-arm-none-eabi
На сайте кросскомпилятора GCC можно скачать требуемую версию GCC-ARM, в данный момент используется версия GCC ARM Embedded 4.7-2013-q1-update. Архив нужно распаковать в выбранный каталог, в переменную окружения Path операционной системы путь к каталогу disk:\path_to_directory\gcc-arm-none-eabi-4_7-2013q1-20130313-win32\bin добавлять нет необходимости, он явно задан в Makefile проекта, который описывается в следующей заметке.
4. Библиотека периферии для контроллеров AT91SAM7Sxx демонстрационной платы AT91SAM7S-EK
Для доступа к регистрам контроллеров по их псевдонимам, а не по адресам, требуется скачать библиотеки периферии. На сайте Atmel найти их можно по ключевым словам 'demoboard-name Software Package for IAR 5.2, Keil and GNU'. Кроме того, архив содержит некоторые утилиты и драйверы, которые могут быть полезны в разработке (TWI, LCD, Ethernet и другие).
5. Библиотека периферии для контроллеров STM32F3XX
Архив с библиотеками периферии. В архиве находятся следующие каталоги:
Libraries/CMSIS (Cortex Microcontroller Software Interface Standard -- стандартные определения для контроллеров на ядре ARM Cortex); Libraries/STM32F37x_I2C_CPAL_Driver (исходные коды и заголовочные файлы библиотеки шины I2C); Libraries/STM32F37x_StdPeriph_Driver (исходные коды и заголовочные файлы библиотеки работы с периферией контроллера).
Project/STM32F37x_StdPeriph_Examples (примеры использования библиотеки CMSIS); Project/STM32F37x_StdPeriph_Templates (шаблоны проектов для различных средств разработки).
Utilities/STM32_EVAL/Common (шрифты для ЖКИ-дисплея отладочной платы STM32373C-EVAL и функции вывода); Utilities/STM32_EVAL/STM32373C_EVAL (базовые исходные коды и заголовочные файлы для периферии, размещённой на отладочной плате).
6. Отладчик OpenOCD
Для программирования контроллера платы AT91SAM7S-EK использовался USB-JTAG адаптер J-Link-ARM, плата STM32373C-EVAL может программироваться как через JTAG, так и посредством размещённого на ней же USB-адаптера ST-LINK V2. Для программирования обеих плат использовалась программа OpenOCD.
7. Установка драйверов libUsb для адаптеров JLink и ST-LINK V2 с помощью утилиты zadig
После подключения к компьютеру адаптеров они не распознаются системой автоматически. Для их корректной работы с OpenOCD нужно скачать утилиту zadig, с помощью которой производится установка драйверов USB для адаптеров. После её запуска в выпадающем списке будут показаны USB-устройства, для которых не установлены драйверы. В списке драйверов (Target Driver) следует выбрать драйвер libusb-win32 (v1.2.6.0), и нажать кнопку Install Driver.

На этом развёртывание среды сборки можно считать завершённым.

четверг, 29 ноября 2012 г.

Boost Spirit::Qi, разбор выражений с числом элементов, большим десяти

При попытке разобрать парсером Spirit::Qi выражение, в котором более десяти элементов, компилятор сообщает об ошибках вида error: '_10' is not a member of 'qi'. Продолжительные и нерегулярные :-) раскопки исходников Qi, а так же связанных с ним Phoenix-а и fusion-а, в конце концов привели к следующим определениям, которые нужно изменить в нужную для себя сторону:
#define PHOENIX_LIMIT                   20 /* Или сколько там надо их парсить... */
#define FUSION_MAX_VECTOR_SIZE          PHOENIX_LIMIT
#define BOOST_RESULT_OF_NUM_ARGS        PHOENIX_LIMIT

После их явного задания требуемые данные успешно распарсились в вектор кортежей:
namespace ph = boost::phoenix;
namespace qi = boost::spirit::qi;
using boost::spirit::ascii::space_type;
//--------------------------------------------------------------------------
typedef double ValueType;
typedef ValueType First;        static const size_t FirstIndex = 0;
typedef ValueType Second;       static const size_t SecondIndex = FirstIndex + 1;
typedef ValueType Third;        static const size_t ThirdIndex = SecondIndex + 1;
typedef ValueType Fourth;       static const size_t FourthIndex = ThirdIndex + 1;
typedef ValueType Fifth;        static const size_t FifthIndex = FourthIndex + 1;
typedef ValueType Sixth;        static const size_t SixthIndex = FifthIndex + 1;
typedef ValueType Seventh;      static const size_t SeventhIndex = SixthIndex + 1;
typedef ValueType Eight;        static const size_t EightIndex = SeventhIndex + 1;
typedef ValueType Ninth;        static const size_t NinthIndex = EightIndex + 1;
typedef ValueType Tenth;        static const size_t TenthIndex = NinthIndex + 1;
typedef ValueType Eleventh;     static const size_t EleventhIndex = TenthIndex + 1;
typedef ValueType Twelfth;      static const size_t TwelfthIndex = EleventhIndex + 1;
typedef bool Locked;            static const size_t LockedIndex = TwelfthIndex + 1;
//--------------------------------------------------------------------------
typedef std::tuple<First,Second,Third,Fourth,Fifth,Sixth,       \
                   Seventh,Eight,Ninth,Tenth,Eleventh,Twelfth,  \
                   Locked> Data;
typedef std::vector<Data> DataContainer;
//--------------------------------------------------------------------------
struct phoenix_make_tuple_impl
{ /* Tuple create adapter  */
  template <typename... Args>
  struct result
  {
    typedef std::tuple<Args...> type;
  };
  template <typename... Args>
  typename result<Args...>::type operator () (Args... args) const
  {
    return std::make_tuple(args...);
  }
};
ph::function<phoenix_make_tuple_impl> const phoenix_make_tuple = phoenix_make_tuple_impl();
//--------------------------------------------------------------------------
template <typename Iterator,typename Container> \
struct ParserGrammar : qi::grammar              \
<Iterator,Container(),qi::blank_type>           \
{
  typedef typename Container::value_type Value;
  ParserGrammar(Container& container) :         \
    ParserGrammar::base_type(start)
  {
    start = qi::lit("Data") >> qi::lit("=") >>     \
      containerData                                \
      [ph::push_back(ph::ref(container),qi::_1)] % \
      qi::eol;
    containerData =                                     \
      (first >> second >> third >> fourth >>            \
       fifth >> sixth >> seventh >> eight >>            \
       ninth >> tenth >> eleventh >> twelfth >>         \
       locked)                                          \
      [qi::_val = phoenix_make_tuple                    \
       (qi::_1,qi::_2,qi::_3,qi::_4,qi::_5,qi::_6,      \
        qi::_7,qi::_8,qi::_9,qi::_10,qi::_11,qi::_12,   \
        qi::_13)];
    first = second = third = fourth = fifth =         \
      sixth = seventh = eight = ninth = tenth =       \
      eleventh = twelfth = qi::double_;
    locked = qi::bool_;
    return;
  }
  qi::rule<Iterator,Container(), qi::blank_type> start;
  qi::rule<Iterator,Value(), qi::blank_type> containerData;
  qi::rule<Iterator,double()>                   \
    first,second,third,fourth,fifth,sixth,      \
    seventh,eight,ninth,tenth,eleventh,twelfth;
  qi::rule<Iterator, bool()> locked;
};
//--------------------------------------------------------------------------
const std::string dataString = "Data = 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 12.12\n\
10.10 20.20 30.30 40.40 50.50 60.60 70.70 80.80 90.90 100.100 110.110 120.120\n";


вторник, 24 июля 2012 г.

Компиляция проектов Qt версии ниже 4.8.1 с использованием MinGW GCC 4.7.0

При компиляции проектов Qt версии ниже 4.8.1 компилятором MinGW GCC 4.7.0 с использованием флага -std=c++11 некоторые макросы распознаются как пользовательские литералы, что приводит к ошибкам компиляции. Для версии Qt 4.8.1 выпущен патч, а для предыдущих версий предлагается следующий заголовочный файл, корректно (с точки зрения C++11) определяющий нужные макросы:
#ifndef CXX0XCOMPATIBILITY_HPP__20120723__1427
#define CXX0XCOMPATIBILITY_HPP__20120723__1427

/*
 * For compatibility with C++-0x mode in GCC 4.7.x
 */
//-------------------------------------------------------------------------
# ifndef QT_NO_DEBUG
#  ifdef QLOCATION
#  undef QLOCATION
#  define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
#   ifndef QT_NO_KEYWORDS
#    ifdef METHOD
#    undef METHOD
#    define METHOD(a)   qFlagLocation("0" #a QLOCATION)
#    endif
#   endif
#  endif
#  ifdef SLOT
#  undef SLOT
#  define SLOT(a)     qFlagLocation("1" #a QLOCATION)
#  endif
#  ifdef SIGNAL
#  undef SIGNAL
#  define SIGNAL(a)   qFlagLocation("2" #a QLOCATION)
#  endif
# else
#  ifndef QT_NO_KEYWORDS
#   ifdef METHOD
#   undef METHOD
#   define METHOD(a)   "0" #a
#   endif
#  endif
#  ifdef SLOT
#  undef SLOT
#  define SLOT(a)     "1" #a
#  endif
#  ifdef SIGNAL
#  undef SIGNAL
#  define SIGNAL(a)   "2" #a
#  endif
# endif

#endif//

Включение этого файла после подключенных заголовочных файлов Qt позволяет "малой кровью", без пересборки самой библиотеки Qt, решить проблему этой ошибки.

воскресенье, 26 февраля 2012 г.

Прочистка форсунок стеклоомывателя

При езде в сырую погоду часто случается досадная неприятность: грязью из-под колёс забиваются форсунки стеклоомывателя. После чего на очистку лобового (а у кого и заднего) стекла приходится тратить гораздо больше времени. Разумеется, это очень снижает безопасность вождения. При попытке прочистить каналы форсунок простой проволокой, зачастую, грязь только проталкивается дальше, к трубке подачи жидкости. В худшем случае проволока может обломиться внутри форсунки, и тогда её остаётся только заменить на новую. А до автомагазина ещё надо доехать... Решение по прочистке было найдено неожиданно, и оказалось очень эффективным. Прочищать надо такой специальной стоматологической штуковиной, под названием эндодонтический файл. Можно выпросить его (с подходящим диаметром) в стоматологическом кабинете, а можно купить в магазине, торгующем медтехникой. Цена их крайне мала -- от 10 рублей за штуку, делаются они из прочной, гибкой стали, так что сломать его внутри форсунки почти невозможно. Так же он имеет конусообразную форму, и резьбу, что позволяет вкрутить его в скопление грязи/ржавчины/чем-там-ещё-может-забиться-форсунка, и вытащить всё это богатство наружу. В общем -- рекомендую. Очень полезная штука.

четверг, 29 декабря 2011 г.

Promote widget в Qt Creator: что это такое, и как его использовать

При создании формы графического интерфейса в Qt Creator-е зачастую возникает необходимость изменить поведение размещённых на форме визуальных компонентов. Для этого в нём (Creator-е) существует механизм promote-инга (корректный перевод не знаю). В качестве примера рассмотрим добавление объекту QLabel возможности реагирования на отпускание на нём левой кнопки мыши (ЛКМ).
Выделив необходимый компонент на форме, в меню ПКМ выбираем пункт Promote to.... Откроется меню задания имени класса-обёртки, и его заголовочного файла. Заголовочный файл может находиться и не в рабочем каталоге с файлом формы (*.ui), можно задать ему либо абсолютный путь (возможны проблемы при переносе проекта между платформами), либо относительный, от рабочего каталога. Дадим классу имя TestPromoter (Promoted class name:), его заголовочный файл -- ./TestPromoter.hpp (Header file:). Нажимаем кнопку Add, и QLabel оказывается "обёрнут" в созданный класс promoter-а.
Теперь нужно вручную создать заголовочный файл, и файл реализации нового класса:
#include <QLabel>
#include <QMouseEvent>
//-------------------------------------------------------------------------
/*
 TestPromoter.hpp
*/
//-------------------------------------------------------------------------
class TestPromoter : public QLabel
{
 Q_OBJECT:
protected:
 virtual void mouseReleaseEvent(QMouseEvent* ev);
public:
 TestPromoter(QWidget* parent = 0,Qt::WindowFlags f = 0);
 TestPromoter(const QString& text,QWidget* parent = 0,Qt::WindowFlags f = 0);
};

#include "./TestPromoter.hpp"
//-------------------------------------------------------------------------
/*
 TestPromoter.cpp
*/
//-------------------------------------------------------------------------
TestPromoter::TestPromoter(QWidget* parent,Qt::WindowFlags f) : \
  QLabel(parent,f) \
{
 return;
}
TestPromoter::TestPromoter(const QString& text,QWidget* parent,Qt::WindowFlags f) : \
  QLabel(text,parent,f) \
{
 return;
}
void TestPromoter::mouseReleaseEvent(QMouseEvent* ev)
{
 if(ev->button() == Qt::LeftButton)
 {
  //Код, который нужно выполнить по указанному событию
 }
 QLabel::mouseReleaseEvent(ev);
 return;
}

Таким образом можно реализовать расширение функциональности визуальных объектов Qt Creator-а.

вторник, 8 ноября 2011 г.

Сборка и установка визуальных плагинов Qt под Windows

В процессе работы с библиотекой Qt возникла необходимость создания своих визуальных компонент-плагинов для Qt Creator-а. Эта задача разделяется на следующие: создание класса визуального компонента (как правило, наследника QWidget); создание класса регистрации плагина в Qt Creator-е; обеспечение возможности сборки как release-, так и debug-версии программы, в которой используется данный плагин. Рассмотрим решение каждой из этих задач для Qt 2010.05 for Windows.
Создание класса визуального компонента происходит обычно, за исключением необходимости добавления одного макроса. Выглядит это приблизительно так:
#include <QWidget>
#include <QtDesigner/QDesignerExportWidget>
//-----------------------------------------------
class QDESIGNER_WIDGET_EXPORT NewClass : public QWidget
{
private:
public:
 explicit NewClass(QWidget* parent = 0);
 ~NewClass(void);
};

Саму реализацию, для простоты, не привожу, можно отнаследоваться от QPushButton, чтобы результат был более нагляден.
Класс QDesignerExportWidget через этот макрос обеспечивает доступ Qt Designer-а к создаваемому классу плагина, и позволяет создавать его экземпляры "на лету", в процессе "таскания мышкой компонентов".
Для регистрации плагина создаётся отдельный класс. Вот как может выглядеть его заголовочный файл:
#include <QtPlugin>
#include <QDesignerCustomWidgetInterface>
//-----------------------------------------------
#include "./NewClass.hpp" //Оба класса находятся в одном каталоге
//-----------------------------------------------
class NewClassPlugin : public QObject,public QDesignerCustomWidgetInterface
{
  Q_OBJECT;
  Q_INTERFACES(QDesignerCustomWidgetInterface);
private:
  bool initialized;
public:
  NewClassPlugin(QObject* parent = 0);
  bool isContainer(void) const;
  bool isInitialized(void) const;
  QIcon icon(void) const;
  QString domXml(void) const;
  QString group(void) const;
  QString includeFile(void) const;
  QString name(void) const;
  QString toolTip(void) const;
  QString whatsThis(void) const;
  QWidget* createWidget(QWidget* parent);
  void initialize(QDesignerFormEditorInterface* core);
};
//-----------------------------------------------

О "наполнении" этих функций можно посмотреть официальную документацию, никаких расхождений с ней не наблюдалось.
Теперь приступаем к сборке и установке плагина. Для ясности дальнейших действий немного о структуре разрабатываемого мной приложения, в котором будет использоваться плагин. Приложение состоит из загрузчика, и динамических библиотек-модулей, которые он загружает, предоставляя некое внутреннее API для функционирования. Как загрузчик, так и модули расположены в индивидуальных поддиректориях общей директории проекта. В директории плагина (пусть её название -- newPlugin) создаём файл проекта newPlugin.pro со следующим содержимым:
TEMPLATE = lib
TARGET = $$qtLibraryTarget($$TARGET)
CONFIG += designer plugin
QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/designer

DLL = $$TARGET".dll"

CONFIG(release, debug | release): QMAKE_POST_LINK = xcopy \
.\\release\\$$DLL ..\\loader\\release /Y

CONFIG(debug, debug | release): QMAKE_POST_LINK = xcopy \
.\\debug\\$$DLL ..\\loader\\debug /Y

# Input
HEADERS += ./newClass.hpp \
           ./newClassPlugin.hpp

SOURCES += ./newClass.cpp \
           ./newClassPlugin.cpp

target.path = $$[QT_INSTALL_PLUGINS]/designer
sources.files = $$HEADERS $$SOURCES *.pro
sources.path = $$[QT_INSTALL_EXAMPLES]/designer/newClass
INSTALLS += target sources

В строке 6 определяется переменная с именем создаваемой DLL, которое будет различным для release- и debug-версий. Строки 8-9 и 11-12 производят копирование средствами Windows собранных DLL плагина в соответствующие каталоги загрузчика. После выполнения команд qmake и mingw32-make all нужно произвести установку собранного плагина в соответствующие каталоги Qt Creator-а. Поскольку он собран, как release-версия, то установка производится командой mingw32-make release-install. Удаляется плагин, соответственно, командой mingw32-make release-uninstall.
И заключительный этап -- сборка модуля, использующего плагин. Поскольку в модуле будет использоваться форма, созданная с помощью Qt Creator-а, то явное подключение заголовочного файла плагина не потребуется. Для модуля создаём файл проекта moduleName.pro:
TEMPLATE = lib
TARGET = 
DEPENDPATH += .
INCLUDEPATH += . $$[QT_INSTALL_EXAMPLES]/designer/newPlugin
CONFIG += thread dll rtti stl exceptions

CONFIG(release, debug | release): LIBS += \
-L"../newPlugin/release" -lnewPlugin

CONFIG(debug, debug | release): LIBS += \
-L"../newPlugin/debug" -lnewPlugind

# Input
FORMS += ./formWithPlugin.ui

HEADERS += ./module.hpp

SOURCES += ./module.cpp

Здесь в строке 4 происходит задание директории, в которой находятся заголовочные файлы плагина. Строки 7-8 и 10-11 задают линковку для отладочной (-lnewPlugind) и релизной (-lnewPlugin) версий. В строке 14 подключается файл описания формы, созданной Qt Creator-ом. Выполнив команды qmake и mingw32-make all, получаем работающий и в release-, и в debug-версиях плагин.

Upd. По неясной пока причине статические библиотеки плагина начали собираться с постфиксом 4, т.е. вместо -lnewPlugin создаётся -lnewPlugin4. В файл проекта, использующего плагин, следует внести соответствующие изменения.

пятница, 28 октября 2011 г.

Количество page faults-ов, как индикатор проблем в работе приложения

В разрабатываемой модульной системе возникли проблемы: после нескольких часов непрерывной работы программа падала по исключению std::bad_alloc. А работать она должна в режиме 24/7 в течении нескольких недель. Первое, что я заподозрил, было утечка памяти, либо дескрипторов. Но проверка Process Explorer-ом ни того, ни другого не выявила. И тут я случайно увидел, что счётчик Page Faults принимает какие-то уж совсем запредельные значения, и непрерывно их увеличивает. В конце концов оказалось, что один из старых модулей при работе с потоками вместо join-а выполнял просто detach, что явно не проявлялось до момента падения программы. Ошибка была исправлена, счётчик Page Faults-ов перестал показывать внешний госдолг США, программа перестала падать на ровном месте.

четверг, 13 октября 2011 г.

Установка VirtualBox на Windows7 в случае вынесенного в сеть домашнего каталога пользователя

Решили наши администраторы вынести каталоги пользователей на отдельную машину в сети. Решили -- и вынесли. Всё было бы хорошо, да только некоторые программы отказывались устанавливаться. Одной из них и была VirtualBox. В процессе установки он говорил, что %UserName% не является корректным именем файла, и аварийно завершался. Решение оказалось простейшим: до установки отключить сетевой адаптер. Так я обновил VirtualBox.

понедельник, 26 сентября 2011 г.

Контролируемый выход из цикла в потоке

В своей деятельности я часто использую потоки. Как правило, из библиотеки boost::thread. На пару с boost::bind возможности получаются впечатляющие. Если поток однократно выполняет какое-либо действие, и сам завершается -- всё хорошо. Но зачастую требуется, чтобы поток выполнял это действие в цикле, через заданный временной интервал, и был способен получать уведомления о завершении своей работы от основного потока приложения. Для подобных целей я использую следующий механизм, основанный на совместном использовании boost::shared_mutex и boost::shared_lock:
#define BOOST_THREAD_USE_LIB
//-------------------------------------------------------------------------
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
//-------------------------------------------------------------------------
#include <string>
#include <iostream>
//-------------------------------------------------------------------------
/*
 g++ -Wall -o ./main ./main.cpp -lboost_thread
*/
//-------------------------------------------------------------------------
class Example
{
private:
 typedef boost::shared_mutex WaiterSharedMutex;
 typedef boost::shared_lock<WaiterSharedMutex> WaiterSharedLock;
 typedef boost::defer_lock_t DeferredLock;
 boost::thread workThread;
 WaiterSharedMutex waiterMutex;
 boost::posix_time::time_duration sleepTime;
 void WorkThread(void)
 {
  while(true)
  {
   WaiterSharedLock wsl(waiterMutex,DeferredLock());
   if(wsl.timed_lock(boost::get_system_time() + sleepTime))
   {
    std::cerr << "Forced exit from thread" << std::endl;
    return;
   }
   std::cerr << "Work of thread" << std::endl;
  }
  return;
 }
public:
 Example(std::size_t timeToSleep) : sleepTime(boost::posix_time::milliseconds(timeToSleep))
 {
  waiterMutex.lock();
  return;
 }
 ~Example(void)
 {
  waiterMutex.unlock();
  workThread.join();
  return;
 }
 bool Start(void)
 {
  bool result = false;
  if(workThread.joinable() == false)
  {
   waiterMutex.try_lock();
   workThread = boost::thread(boost::bind(&Example::WorkThread,this));
   result = true;
  }
  return result;
 }
 bool Stop(void)
 {
  bool result = false;
  waiterMutex.unlock();
  if(workThread.joinable() == true)
  {
   workThread.join();
  }
  return result;
 }
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
 std::string input;
 Example example(50000);
 while(true)
 {
  input.clear();
  std::cout << ":> ";
  std::cin >> input;
  if(!input.compare("quit"))
  {
   break;
  }
  else if(!input.compare("start"))
  {
   example.Start();
  }
  else if(!input.compare("stop"))
  {
   example.Stop();
  }
 }
 return 0;
}
Временная задержка выполнения потока обеспечивается ожиданием на mutex-е функцией timed_lock, а возможность немедленного завершения потока тем, что как сам mutex, так и его блокировка являются разделяемыми (shared_mutex и shared_lock). У разделяемого mutex-а есть две возможных блокировки: эксклюзивная, и разделяемая. Если на mutex-е эксклюзивная блокировка -- заблокировать его разделяемой будет невозможно. Обратное тоже верно. Но эксклюзивную блокировку может создавать только один поток, а разделяемую -- несколько. В представленном примере на mutex ставится эксклюзивная блокировка в конструкторе класса (строка 40), и перед попыткой пересоздать поток (строка 54). Поток (потоки), которому требуется выполнять в цикле некую функцию, пытается получить разделяемую блокировку на mutex (строка 28). Завершиться успехом это может только в случае, если основной поток отпустит эксклюзивную блокировку (строка 63, и вызов функции Stop в деструкторе класса). После чего требуется лишь дождаться завершения потока (потоков) функцией join.

пятница, 8 июля 2011 г.

Файл конфигурации Emacs и Windows 7

При эксплуатации Emacs под Windows 7 у меня возникла проблема с его файлом .emacs. При первоначальном запуске он располагался в C:\Users\%Username%\AppData\Roaming, но по непонятным причинам иногда обнулялся (машина находится в домене). При переносе .emacs в несистемный каталог, и добавлении переменной окружения %HOME%, указывающей на этот каталог, затирание этого файла прекратились. Что странно, на Windows XP такого поведения замечено не было.
Upd: Переменную %HOME% увидели и некоторые другие кроссплатформенные приложения, например, VirtualBox теперь складывает свою конфигурацию в каталог, указанный в ней. Так что в случае использования компьютера более, чем одним человеком, эту переменную лучше записывать, как переменную среды пользователя, а не системную.