разработка и программирование электронных устройств

Пример использования CMSIS в Eclipse

Cortex Microcontroller Software Interface Standard ( CMSIS ) содержит в своем составе программный интерфейс к периферийным модулям ядра и периферии различных микроконтроллеров Cortex-M3(M0).
Из обязательной части стандарт рекомендует использовать доступ к регистрам с помощью указателей на структуры в языке С ( например, доступ к регистру GPIOB_BRR осуществляется как GPIOB->BRR ). Для этого достаточно подключить к проекту заголовочный файл для конкретного микроконтроллера, который содержит включение заголовочного файла ядра core_cm3.h и заголовочного файла system_xxxx.h дополнительных функций CMSIS для конкретной модели xxxx микроконтроллера. Эти файлы должны быть включены в проект , если Вы собираетесь хоть как-то использовать CMSIS.
Необязательная часть стандарта CMSIS содержит различные функции инициализации ядра и периферии. Для использования этих функций необходимо откомпилировать исходные файлы core_cm3.c и system_xxxx.c, где xxxx – общее название микроконтроллера( семейства ). В каталоге CMSIS есть примеры простых проектов для различных инструментальных средств, в том числе и Sourcery G++ Lite, что делает возможным использование компилятора C из коллекции GCC.
Проект предназначен для работы в интегрированной среде разработки Keil uVision 4 с использованием внешнего компилятора GCC. Попытка переделать проект в Eclipse не увенчалась успехом, поскольку часть необходимого для загрузки программы кода добавляется к проекту средой разработки . Пришлось написать стартовый код , скрипт компоновки и Makefile самостоятельно.
Поскольку для операционных систем windows - семейства инструментальных средств предостаточно, то я буду делать уклон на использование Eclipse + CMSIS в Linux ( Fedora Electronic Lab ).

Итак , скачиваем проект ( загрузить ) и копируем его в свое рабочее пространство. Далее делаем импорт проекта в Eclipse( File → Import → General → File System ).


Проект полностью написан на языке С , содержит стартовый файл ( тоже на С ). Также , как и все проекты для ARM под GCC этот имеет скрипт компоновщика и Makefile. Теперь рассмотрим более детально каждый из перечисленных файлов.


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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/* startup_STM32F10x.c */
 
extern void SystemInit( void );
extern const void __cs3_stack;
extern const void _fdata;
extern const void _data;
extern const void _edata;
extern const void _bss;
extern const void _ebss;
 
/* Эти строки объявляют внешние переменные, в которых хранятся адреса начала и конца различных областей памяти. Конкретные адреса присваиваются переменным в скрипте компоновки. */  
 
void __attribute__(( weak )) Reset_Handler( void );
void __attribute__(( weak )) NMI_Handler( void );
void __attribute__(( weak )) HardFault_Handler( void );
void __attribute__(( weak )) MemManage_Handler( void );
void __attribute__(( weak )) BusFault_Handler( void );
void __attribute__(( weak )) UsageFault_Handler( void );
void __attribute__(( weak )) SVC_Handler( void );
void __attribute__(( weak )) DebugMon_Handler( void );
void __attribute__(( weak )) PendSV_Handler( void );
void __attribute__(( weak )) SysTick_Handler( void );
 
/* Объявления обработчиков прерываний ядра Cortex-M3. Атрибут «weak» используется для того, чтобы подставлять обработчики, заданные  по-умолчанию, если пользователь не прописывает свой обработчик вектора прерывания. */
 
__attribute__ ( ( section(".cs3.interrupt_vector" ) ) )
void (* const int_Vectors[] )( void ) = {
		&__cs3_stack,
		Reset_Handler,
		NMI_Handler,
		HardFault_Handler,
		BusFault_Handler,
		UsageFault_Handler,
		0,
		0,
		0,
		0,
		SVC_Handler,
		DebugMon_Handler,
		0,
		PendSV_Handler,
		SysTick_Handler
};
 
/* Прописывает таблицу векторов прерываний в секции с названием ".cs3.interrupt_vector", которая потом будет размещена в секции кода .text, начиная с нулевого адреса. У микроконтроллеров с ядром Cortex-M3 по нулевому адресу нужно прописывать адрес вершины стека,который загружается в регистр SP( stack pointer ), обычно это последний адрес в ОЗУ. Следующий адрес (0x0 + 4) содержит адрес обработчика вектора сброса. Далее со смещением в 4 байта располагаются все остальные векторы прерываний. Таблица векторов конкретных моделей микроконтроллеров, кроме прерываний ядра, может иметь до 240 обработчиков прерываний различных периферийных устройств. */
 
void Default_Handler( void );
 
#pragma weak NMI_Handler = Default_Handler
#pragma weak HardFault_Handler = Default_Handler
#pragma weak MemManage_Handler = Default_Handler
#pragma weak BusFault_Handler = Default_Handler
#pragma weak UsageFault_Handler = Default_Handler
#pragma weak SVC_Handler = Default_Handler
#pragma weak DebugMon_Handler = Default_Handler
#pragma weak PendSV_Handler = Default_Handler
#pragma weak SysTick_Handler = Default_Handler
 
/* Директива #pragma устанавливает обработчики по-умолчанию для неиспользуемых векторов прерываний, в данном случае это обработчик Default_Handler, который содержит бесконечный цикл. */
 
void Default_Handler( void )
{
	while( 1 );
}/* Default_Handler */
 
extern int main( void );
 
/* Далее расположен обработчик вектора сброса. В нем выполняется копирование констант из памяти программ в память данных , производится инициализация нулевыми значениями секции .bss , в которой расположены статические переменные языка С. */
 
void Reset_Handler( void )
{
unsigned int * src, *dst;
 
src = ( unsigned int * )&_fdata;
 
for( dst = ( unsigned int *)&_data; dst < (unsigned int *)&_edata; dst++ )
{
	*dst = *src;
}
 
for( dst = ( unsigned int *)&_bss; dst < (unsigned int *)&_ebss; dst++ )
{
	*dst = 0;
}
	SystemInit();
	main();
	while( 1 );
}/* Reset_Handler */
 
/* Функция  SystemInit является расширением CMSIS и производит начальную инициализацию микроконтроллера. Если Вы не планируете использовать расширенные функции CMSIS – просто закомментируйте эту строчку.
Напоследок выполняется вызов главной функции в языке программирования  С  под названием main. 
После main расположен бесконечный цикл на тот случай , если функция вернет код ошибки( int main( void ) ). */

Скрипт компоновщика STM32F103.ls написан для микроконтроллера STM32F103RBT6, который содержит 128 кБайт памяти программ и 20 кБайт памяти данных. Если объем памяти Вашего микроконтроллера STM32F10x отличается от заданного — подкорректируйте файл скрипта компоновки.

/* STM32F103.ls  */
OUTPUT_FORMAT("elf32-littlearm","elf32-bigarm","elf32-littlearm")
ENTRY( Reset_Handler )
 
MEMORY
{
	flash(rx) : ORIGIN = 0x00000000, LENGTH = 128k
	sram(rwx) : ORIGIN = 0x20000000, LENGTH = 20k
}
SECTIONS
{
	.	= 0x0;
	.text : {
		KEEP( *(.cs3.interrupt_vector));
		*(.text .text.* );
		*(.rodata .rodata.* );
		_fdata = .;
	} > flash
	.=0x20000000;
	.data : {
		_data = .;
		*(.data .data.* );
		_edata = .;		
	} > sram
	.bss : {
		_bss = .;
		*(.bss .bss.* );
		_ebss = .; 
	} > sram
	__cs3_stack = 0x20000000 + ( 20*1024 );
}

В теле MEMORY именуются доступные области памяти, устанавливаются права доступа ( rwx ), описываются начальные адреса и размеры областей.
В теле SECTIONS описываются доступные секции, основные из которых .text (память программ), .data ( память данных ) и .bss( секция переменных с нулевым начальным значением ).

Для сегмента кода устанавливается начальный адрес 0×00000000, туда ( в .text ) впихивается секция векторов прерываний .cs3.interrupt_vector. Звездочка в начале означает, что если Вы пожелаете разнести таблицу векторов по разным файлам, то все эти области будут сложены в одну. Также секции .text и .rodata из всех исходных файлов будут скомпонованы в одну секцию .text и .rodata соответственно.

Переменная __cs3_stack будет содержать последний адрес в области памяти данных.
Итак, постепенно мы переходим к последнему файлу из рассматриваемого списка — Makefile.

Макеfile для проекта с использованием CMSIS ничем не отличается от мейкфайла для любого другого проекта, за исключением ключа компиляции -DSTM32F10X_MD, который аналогичен директиве #define STM32F10X_MD и определяет имя STM32F10X_MD, необходимое для подстановки в файлах CMSIS строк, соответствующих классификации MEDIUM STM32.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
.PHONY: all clean program
 
CROSS_COMPILE 				= arm-none-eabi-
CHIP						=	cortex-m3
OPT_LEVEL 					= 0
DEBUG_FORMAT 				= dwarf-2
 
OUT_DIR						= ./out
OBJ_DIR						= ./obj
 
PROJECT_NAME 				= CMSIS_example
OBJECTS 					= $(OBJ_DIR)/startup_STM32F10x.o $(OBJ_DIR)/main.o $(OBJ_DIR)/core_cm3.o $(OBJ_DIR)/system_stm32f10x.o
ELF_FILE					= $(OUT_DIR)/$(PROJECT_NAME).out
BIN_FILE					= $(OUT_DIR)/$(PROJECT_NAME).bin
LST_FILE					= $(OUT_DIR)/$(PROJECT_NAME).lst
MAP_FILE					= $(OUT_DIR)/$(PROJECT_NAME).map
LD_SCRIPT					= STM32F103.ls
 
DEFS						= -DSTM32F10X_MD
 
CC 							= $(CROSS_COMPILE)gcc
LD 							= $(CROSS_COMPILE)gcc
AS 							= $(CROSS_COMPILE)gcc
OC							= $(CROSS_COMPILE)objcopy
OD							= $(CROSS_COMPILE)objdump
PROG						= openocd
 
 
CCFLAGS 					= -Wall -ffunction-sections -fdata-sections -g$(DEBUG_FORMAT) -mcpu=$(CHIP) -c -mthumb -o$(OPT_LEVEL) $(DEFS)
LDFLAGS 					= -Wl,--gc-sections -Wl,-Map,$(MAP_FILE) -Wl,-T$(LD_SCRIPT) 
OCFLAGS						= -O binary
ODFLAGS						= -S -D
 
PROG_CL						= -d0
PROG_CL						+= -f interface/ftdi_jtag.cfg -f target/stm32.cfg
PROG_CL						+= -c init -c targets	
PROG_CL						+= -c "halt"
PROG_CL						+= -c "flash probe 0"
PROG_CL						+= -c "flash write_image erase $(ELF_FILE) 0x08000000 elf"
PROG_CL						+= -c "reset run"
PROG_CL						+= -c shutdown
 
all : $(BIN_FILE) $(LST_FILE)
	@echo "*** building is completed!!! ***"	
 
$(BIN_FILE) : $(ELF_FILE)
	$(OC) $(OCFLAGS) $(ELF_FILE) $(BIN_FILE)
 
$(LST_FILE) : $(ELF_FILE)
	$(OD) $(ODFLAGS) $(ELF_FILE) > $(LST_FILE)
 
$(ELF_FILE) : $(OBJECTS) $(LD_SCRIPT)
	$(LD) $(LDFLAGS) -o $(ELF_FILE) $(OBJECTS)  
 
$(OBJ_DIR)/main.o : main.c
	$(CC) $(CCFLAGS) main.c -o $(OBJ_DIR)/main.o 
 
$(OBJ_DIR)/startup_STM32F10x.o : startup_STM32F10x.c
	$(CC) $(CCFLAGS) startup_STM32F10x.c -o $(OBJ_DIR)/startup_STM32F10x.o
 
$(OBJ_DIR)/core_cm3.o : core_cm3.c
	$(CC) $(CCFLAGS) core_cm3.c -o $(OBJ_DIR)/core_cm3.o
 
$(OBJ_DIR)/system_stm32f10x.o : system_stm32f10x.c
	$(CC) $(CCFLAGS) system_stm32f10x.c -o $(OBJ_DIR)/system_stm32f10x.o
 
clean:
	rm -f $(OBJ_DIR)/*.o $(OUT_DIR)/*.out $(OUT_DIR)/*.lst $(OUT_DIR)/*.map $(OUT_DIR)/*.bin
	@echo "*** cleaning is completed!!! ***"
 
program : $(ELF_FILE)
	$(PROG) $(PROG_CL)
	@echo "*** programming is completed!!! ***"

В качестве программатора используется openocd. Для того, чтобы прошить программу в память микроконтроллера, необходимо всего лишь запустить цель «Make program» из окна «Make targets» в Eclipse.

Наконец-то я убрал из Makefile абсолютный путь к каталогу bin пакета Sourcery G++ Lite в Fedora Electronic Lab. Для этого пришлось вручную добавить строчку PATH=$PATH:/usr/local/CodeSourcery/Sourcery_G++_Lite/bin/ в файл /etc/profile.
Вторая проблема, с которой я столкнулся , работая в Fedora Linux, – это отказ openocd распознавать адаптер FTDI JTAG. Проблема решается запуском openocd с правами пользователя root. Чтобы избежать всяческих неприятных моментов, связанных с ограничениями прав доступа, я запускаю Eclipse из-под root-а.
Кто работал в Linux, знает , что графический вход в систему для суперпользователя root заблокирован в целях безопасности. Найденные в сети интернет способы разблокироть графический вход под root-ом в Fedora Electronic Lab не приведут к желаемому результату, поэтому единственным вариантом остается запускать Eclipse из терминала с правами root-а.

$su
$<password>
#/usr/bin/eclipse

Рабочее пространство располагается в каталоге /root/workspace.
Для копирования файлов я использую менеджер файлов, подобный Norton(Volkov) Commander-у, который называется Midnight Commander( mc ), для его установки необходимо выполнить следующую команду в терминале при наличии соединения с сетью интернет.

#yum install mc

Запуск менеджера файлов осуществляется командой mc.

#mc

Теперь хочу сказать пару слов о самой программе. Программа предельно проста, она осуществляет поочередную установку и сброс PC12 микроконтроллера STM32F103RBT6 для управления светодиодом, который расположен на этой линии порта C в отладочной плате STM32F103-HB. Для создания временной задержки используется цикл for, значение параметра задержки функции Delay приблизительно соответсвует значению в миллисекундах.

2 Comments to Пример использования CMSIS в Eclipse

  1. mackrolls's Gravatar mackrolls
    9 апреля 2011 at 13:58 | Permalink

    Здравствуйте!
    У меня вопрос по работе с Code Sourcery. Заголовочные файлы можно инклюдить в кавычках и в угловых скобках. Я так понимаю, если написать имя в угловых скобках, то code sourcery возьмет заголовочный файл из той папки, куда codesourcery установлен. Но один заголовочный файл может лежать в разных подпапках, например J:\ARM\Sourcery 2010.09-51\arm-none-eabi\include\stdint.h и J:\ARM\Sourcery 2010.09-51\lib\gcc\arm-none-eabi\4.5.1\include\stdint.h. Где прописано какой именно файл используется?
    И с подключением библиотек тоже не могу разобраться. Пути к библиотекам нужно всегда конкретно указывать линкеру директивой -L? Зачем тогда директива -nostdlib?

Leave a Reply

You must be logged in to post a comment.