четверг, 10 марта 2011 г.

Настройка среды разработки

Когда уже имеется работающий копилятор, можно писать программы и собирать их с помощью командной строки, но гораздо удобнее делать все это с помощью любимой среды разработки (IDE, Integrated Development Environment).




Среда разработки 

В качестве IDE я выбрал Code::Blocks 10.05, потому что Eclipse мне разонравилось из-за своей тяжеловесности и приверженности Java.
Сначала попробовал воспользоваться встроенными в CB шаблонами проектов для ARM, но они оказались предназначенными для конкретныех отладочных плат, среди них даже не нашлось ни одной на нужном мне процессоре.
Поэтому пришлось создать пустой проект и настроить там все вручную.
Первым делом, создав пустой проект, нужно пойти на "Settings->Compiler and Debugger settings" и в разделе "Global compiler settings" выбрать GNU ARM Compilator. 
Сначала нужно указать, где находятся исполняемые файлы компилятора и утилит, для этого надо перейти на вкладку "Toolchain executables" и указать там пути и названия. Автообнаружение компилятора не сработало, поэтому я руками вписал путь (/usr/arm) и ко всем утилитам ниже добавил приставку "arm-none-eabi-". Получилось очень красиво.
Еще на вкладке "Other settings" нужно выбрать значение для "Compiler logging": "Full command line", чтобы видеть в журнале компиляции все, что происходит.
На этом настройка глобальных параметров компилятора закончена.

Теперь настройка сборки проекта: "Project->Build Options". 
Здесь нужно в самом левом окошке выбрать название проекта, тогда настройки будут для всех вариантов сборки - и Debug, и Release.
На вкладке "Compiler settings->Other settings" вписал параметры "-mcpu=cortex-m3 -mthumb -fno-exceptions", больше никаких флагов трогать не стал. В процессорах STM32F103x нет сопроцессора, поэтому нужно использовать программный вариант арифметики с плавающей запятой (soft), что считается параметром по умолчанию.
"-mcpu=cortex-m3" - выбирается тип процессора,
"-mthumb" - система команд, все вместе приведет к использованию семейства команд thumb-2, что в данном случае и нужно.
"-fno-exceptions" - позволяет в несколько раз уменьшить размер выходного файла, потому что выкидывается поддержка исключений для С++, которая в данном случае не нужна. Еще рекомендуют использовать параметр "-fno-rtti", который убирает накладные расходы, связанные с использованием идентификации типов во время исполнения (например, при наследовании).
В настроках компоновщика ("Linker settings") в поле Other linker options" нужно добавитьтакие параметры:
"-mcpu=cortex-m3 -mthumb
-Tstm32.ld
-Wl,-Map=$(TARGET_OUTPUT_FILE).map,--cref,--gc-sections".
"-Tstm32_flash.ld" - для размещения секций памяти, описанных в файле stm32_flash.ld,
"-Wl,-Map=$(TARGET_OUTPUT_FILE).map,--cref,--gc-sections" - чтобы получить карту памяти.
Часто встречается использование параметров
"-nostartfiles" и "-nostdlib", но это нужно делать сугубо индивидуально в зависимости от конкретной ситуации.
"-nostartfiles" -  отключается использование, например, crt0.o, где производится настройка многих вещей в самом начале работы программы: инициализация стека, настройка таблицы векторов прерываний, обнуление  глобальных переменных, присвоение начальных значений. Если имеется свой собственный начальный файл. то стандартный лучше отключить.
"-nostdlib" - аналогично, если нежелательно использование стандартных библиотек libc, libm и libgcc.
Еще я на вкладке "Compiler settings-># defines" добавил следующие определения:
STM32F10X_HD
HSE_VALUE=12000000UL
Они нужны для указания параметров используемого процессора, используются стандартной библиотекой STM32.
На вкладке "Search directories" все поля пустые.
На вкладке "Pre/post build steps" в поле "Post-build steps" я добавил такие строки:
arm-none-eabi-size $(TARGET_OUTPUT_FILE) $(TARGET_OUTPUT_FILE) - чтобы получать информацию о размере используемой памяти,
arm-none-eabi-objcopy -O ihex $(TARGET_OUTPUT_FILE) $(TARGET_OUTPUT_FILE).hex - чтобы получить файл для прошивки,
arm-none-eabi-objcopy -O binary $(TARGET_OUTPUT_FILE) $TARGET_OUTPUT_FILE).bin - собственно, тоже можно использовать для прошивки процессора,
arm-none-eabi-objdump -h -S $(TARGET_OUTPUT_FILE) > $(TARGET_OUTPUT_FILE).lss - чтобы получить дизассемблерный листинг. Для листинга, по другим рекомендациям, можно использовать ключи -D -S - тогда листинг получается в несколько раз больше.

Создание проекта

Поскольку проект был пустой, то пришлось в нем создать новый файл main.cpp такого содержания:

#include "stm32f10x.h"

int main(void)
{
    uint32_t i = 0;
    while(true)
    {
        for (uint32_t k = 0; k < 2000; k++)
            i = k;
    }
    return 0;
}
Текст сделан так, чтобы компилятору можно было хоть что-то сделать. Для поддержки своих процессоров STMicroelectronics  дает возможность загрузить со своего сайта библиотеку поддержки stm32f10x_stdperiph_lib.zip (примерно 14 МБ размером).
В этом пакете есть файл описания процессора STM32F10x_StdPeriph_Lib_V3.3.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h, в нем содержатся названия и привязки битов, регистров и всего прочего для процессора. В нем необходимо примерно в 50-й строке раскомментарить строчку, характеризующую используемый процессор, в моем случае это была строка 53:
#define STM32F10X_HD     /*!< STM32F10X_HD: STM32 High density devices */
или определить в командной строке компилятора параметр -DSTM32F10X_HD. Кроме того, необходимо указать частоту используемого кварца. По умолчанию считается, что используется кварц на 8 МГц, поэтому нужно указать свое значение (-DHSE_VALUE=12000000UL в моем случае, поскольку на плату установили кварц на 12 МГц). Но, к сожалению, этого недостаточно, потому что в функции
SetSysClockTo72() все равно жестко используется умножение на 9:
/*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
что рассчитано только на использование кварца 8 МГц. В моем случае мне нужно было бы в этом файле (system_stm32f10x.h) вместо RCC_CFGR_PLLMULL9 вписать RCC_CFGR_PLLMULL6, либо написать собственную вариант настройки всех частот процессора.
В строке 412 и 413 файла stm32f10x.h видно, что нужны еще два файла:
#include "core_cm3.h"
#include "system_stm32f10x.h"
поэтому их и соответствующие им С-файлы тоже нужно скопировать в свой проект.
Файлы core_cm3.h и core_cm3.c находятся в каталоге STM32F10x_StdPeriph_Lib_V3.3.0/Libraries/CMSIS/CM3/CoreSupport,
 а system_stm32f10x.h и system_stm32f10x.c - рядом с файлом stm32f10x.h.
В core_cm3.* описывается то, что односится к ядру cortex-m3, а в остальных файлах - периферия.
Периферийные описания, оказывается, можно брать и из другого каталога: STM32F10x_StdPeriph_Lib_V3.3.0/Libraries/STM32F10x_StdPeriph_Driver. Там находятся небольшие программные модули для обслуживания той или иной периферии. Но, чтобы не заморачиваться, еще и с интерфейсом этих вызовов, я использую базовую конфигурацию, а чтобы понять, как работать с тем или иным устройством процессора, заглядываю в этот каталог.
Для правильной инициализации некоторых вещей еще до передачи управления С-программе используется стартовый модуль на ассемблере, который можно взять из каталога STM32F10x_StdPeriph_Lib_V3.3.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7 (потому что компилятор Raisonance построен на базе GCC) - для процессора STM32103VCT подходит файл startup_stm32f10x_hd.s.
Но я нашел примеры стартовых файлов на С и решил использовать именно такой файл:

#include "stm32f10x.h"

typedef void( *const intfunc )( void );

#define WEAK __attribute__ ((weak))

extern unsigned long _etext;
extern unsigned long _sidata;
extern unsigned long _sdata;
extern unsigned long _edata;
extern unsigned long _sbss;
extern unsigned long _ebss;
extern unsigned long _estack;

void Reset_Handler(void) __attribute__((__interrupt__));
void __Init_Data(void);
void Default_Handler(void);

extern int main(void);
extern void __libc_init_array(void);  /* calls CTORS of static objects */

void WEAK NMI_Handler(void);
void WEAK HardFault_Handler(void);
void WEAK MemManage_Handler(void);
void WEAK BusFault_Handler(void);
void WEAK UsageFault_Handler(void);
void WEAK MemManage_Handler(void);
void WEAK SVC_Handler(void);
void WEAK DebugMon_Handler(void);
void WEAK PendSV_Handler(void);
void WEAK SysTick_Handler(void);

void WEAK WWDG_IRQHandler(void);
void WEAK PVD_IRQHandler(void);
void WEAK TAMPER_IRQHandler(void);
void WEAK RTC_IRQHandler(void);
void WEAK FLASH_IRQHandler(void);
void WEAK RCC_IRQHandler(void);
void WEAK EXTI0_IRQHandler(void);
void WEAK EXTI1_IRQHandler(void);
void WEAK EXTI2_IRQHandler(void);
void WEAK EXTI3_IRQHandler(void);
void WEAK EXTI4_IRQHandler(void);
void WEAK DMA1_Channel1_IRQHandler(void);
void WEAK DMA1_Channel2_IRQHandler(void);
void WEAK DMA1_Channel3_IRQHandler(void);
void WEAK DMA1_Channel4_IRQHandler(void);
void WEAK DMA1_Channel5_IRQHandler(void);
void WEAK DMA1_Channel6_IRQHandler(void);
void WEAK DMA1_Channel7_IRQHandler(void);
void WEAK ADC1_2_IRQHandler(void);
void WEAK USB_HP_CAN1_TX_IRQHandler(void);
void WEAK USB_LP_CAN1_RX0_IRQHandler(void);
void WEAK CAN1_RX1_IRQHandler(void);
void WEAK CAN1_SCE_IRQHandler(void);
void WEAK EXTI9_5_IRQHandler(void);
void WEAK TIM1_BRK_IRQHandler(void);
void WEAK TIM1_UP_IRQHandler(void);
void WEAK TIM1_TRG_COM_IRQHandler(void);
void WEAK TIM1_CC_IRQHandler(void);
void WEAK TIM2_IRQHandler(void);
void WEAK TIM3_IRQHandler(void);
void WEAK TIM4_IRQHandler(void);
void WEAK I2C1_EV_IRQHandler(void);
void WEAK I2C1_ER_IRQHandler(void);
void WEAK I2C2_EV_IRQHandler(void);
void WEAK I2C2_ER_IRQHandler(void);
void WEAK SPI1_IRQHandler(void);
void WEAK SPI2_IRQHandler(void);
void WEAK USART1_IRQHandler(void);
void WEAK USART2_IRQHandler(void);
void WEAK USART3_IRQHandler(void);
void WEAK EXTI15_10_IRQHandler(void);
void WEAK RTCAlarm_IRQHandler(void);
void WEAK USBWakeUp_IRQHandler(void);
void WEAK TIM8_BRK_IRQHandler(void);
void WEAK TIM8_UP_IRQHandler(void);
void WEAK TIM8_TRG_COM_IRQHandler(void);
void WEAK TIM8_CC_IRQHandler(void);
void WEAK ADC3_IRQHandler(void);
void WEAK FSMC_IRQHandler(void);
void WEAK SDIO_IRQHandler(void);
void WEAK TIM5_IRQHandler(void);
void WEAK SPI3_IRQHandler(void);
void WEAK UART4_IRQHandler(void);
void WEAK UART5_IRQHandler(void);
void WEAK TIM6_IRQHandler(void);
void WEAK TIM7_IRQHandler(void);
void WEAK DMA2_Channel1_IRQHandler(void);
void WEAK DMA2_Channel2_IRQHandler(void);
void WEAK DMA2_Channel3_IRQHandler(void);
void WEAK DMA2_Channel4_5_IRQHandler(void);

__attribute__ ((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
    (intfunc)((unsigned long)&_estack), /* The stack pointer after relocation */
    Reset_Handler,              /* Reset Handler */
    NMI_Handler,                /* NMI Handler */
    HardFault_Handler,          /* Hard Fault Handler */
    MemManage_Handler,          /* MPU Fault Handler */
    BusFault_Handler,           /* Bus Fault Handler */
    UsageFault_Handler,         /* Usage Fault Handler */
    0,                          /* Reserved */
    0,                          /* Reserved */
    0,                          /* Reserved */
    0,                          /* Reserved */
    SVC_Handler,                /* SVCall Handler */
    DebugMon_Handler,           /* Debug Monitor Handler */
    0,                          /* Reserved */
    PendSV_Handler,             /* PendSV Handler */
    SysTick_Handler,            /* SysTick Handler */

    /* External Interrupts */
    WWDG_IRQHandler,            /* Window Watchdog */
    PVD_IRQHandler,             /* PVD through EXTI Line detect */
    TAMPER_IRQHandler,          /* Tamper */
    RTC_IRQHandler,             /* RTC */
    FLASH_IRQHandler,           /* Flash */
    RCC_IRQHandler,             /* RCC */
    EXTI0_IRQHandler,           /* EXTI Line 0 */
    EXTI1_IRQHandler,           /* EXTI Line 1 */
    EXTI2_IRQHandler,           /* EXTI Line 2 */
    EXTI3_IRQHandler,           /* EXTI Line 3 */
    EXTI4_IRQHandler,           /* EXTI Line 4 */
    DMA1_Channel1_IRQHandler,   /* DMA1 Channel 1 */
    DMA1_Channel2_IRQHandler,   /* DMA1 Channel 2 */
    DMA1_Channel3_IRQHandler,   /* DMA1 Channel 3 */
    DMA1_Channel4_IRQHandler,   /* DMA1 Channel 4 */
    DMA1_Channel5_IRQHandler,   /* DMA1 Channel 5 */
    DMA1_Channel6_IRQHandler,   /* DMA1 Channel 6 */
    DMA1_Channel7_IRQHandler,   /* DMA1 Channel 7 */
    ADC1_2_IRQHandler,          /* ADC1 & ADC2 */
    USB_HP_CAN1_TX_IRQHandler,  /* USB High Priority or CAN1 TX */
    USB_LP_CAN1_RX0_IRQHandler, /* USB Low  Priority or CAN1 RX0 */
    CAN1_RX1_IRQHandler,        /* CAN1 RX1 */
    CAN1_SCE_IRQHandler,        /* CAN1 SCE */
    EXTI9_5_IRQHandler,         /* EXTI Line 9..5 */
    TIM1_BRK_IRQHandler,        /* TIM1 Break */
    TIM1_UP_IRQHandler,         /* TIM1 Update */
    TIM1_TRG_COM_IRQHandler,    /* TIM1 Trigger and Commutation */
    TIM1_CC_IRQHandler,         /* TIM1 Capture Compare */
    TIM2_IRQHandler,            /* TIM2 */
    TIM3_IRQHandler,            /* TIM3 */
    TIM4_IRQHandler,            /* TIM4 */
    I2C1_EV_IRQHandler,         /* I2C1 Event */
    I2C1_ER_IRQHandler,         /* I2C1 Error */
    I2C2_EV_IRQHandler,         /* I2C2 Event */
    I2C2_ER_IRQHandler,         /* I2C2 Error */
    SPI1_IRQHandler,            /* SPI1 */
    SPI2_IRQHandler,            /* SPI2 */
    USART1_IRQHandler,          /* USART1 */
    USART2_IRQHandler,          /* USART2 */
    USART3_IRQHandler,          /* USART3 */
    EXTI15_10_IRQHandler,       /* EXTI Line 15..10 */
    RTCAlarm_IRQHandler,        /* RTC Alarm through EXTI Line */
    USBWakeUp_IRQHandler,       /* USB Wakeup from suspend */
    TIM8_BRK_IRQHandler,
    TIM8_UP_IRQHandler,
    TIM8_TRG_COM_IRQHandler,
    TIM8_CC_IRQHandler,
    ADC3_IRQHandler,
    FSMC_IRQHandler,
    SDIO_IRQHandler,
    TIM5_IRQHandler,
    SPI3_IRQHandler,
    UART4_IRQHandler,
    UART5_IRQHandler,
    TIM6_IRQHandler,
    TIM7_IRQHandler,
    DMA2_Channel1_IRQHandler,
    DMA2_Channel2_IRQHandler,
    DMA2_Channel3_IRQHandler,
    DMA2_Channel4_5_IRQHandler,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0,
    (intfunc)0xF1E0F85F         /* @0x1E0. This is for boot in RAM mode for STM32F10x High Density devices. */
};

void __Init_Data(void) {
    unsigned long *src, *dst;
    /* copy the data segment into ram */
    src = &_sidata;
    dst = &_sdata;
    if (src != dst)
        while(dst < &_edata)
            *(dst++) = *(src++);

    /* zero the bss segment */
    dst = &_sbss;
    while(dst < &_ebss)
        *(dst++) = 0;
}

void Reset_Handler(void) {
    /* Initialize data and bss */
    __Init_Data();

    __libc_init_array(); // call all static construcors in C++ (harmless in C programs)
    SCB->VTOR = 0x08000000;
    SystemInit();
    main();
    while(1) {}
}

#pragma weak MMI_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 weak WWDG_IRQHandler        = Default_Handler
#pragma weak PVD_IRQHandler        = Default_Handler
#pragma weak TAMPER_IRQHandler        = Default_Handler
#pragma weak RTC_IRQHandler        = Default_Handler
#pragma weak FLASH_IRQHandler        = Default_Handler
#pragma weak RCC_IRQHandler        = Default_Handler
#pragma weak EXTI0_IRQHandler        = Default_Handler
#pragma weak EXTI1_IRQHandler        = Default_Handler
#pragma weak EXTI2_IRQHandler        = Default_Handler
#pragma weak EXTI3_IRQHandler        = Default_Handler
#pragma weak EXTI4_IRQHandler        = Default_Handler
#pragma weak DMA1_Channel1_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel2_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel3_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel4_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel5_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel6_IRQHandler    = Default_Handler
#pragma weak DMA1_Channel7_IRQHandler    = Default_Handler
#pragma weak ADC1_2_IRQHandler        = Default_Handler
#pragma weak USB_HP_CAN1_TX_IRQHandler    = Default_Handler
#pragma weak USB_LP_CAN1_RX0_IRQHandler    = Default_Handler
#pragma weak CAN1_RX1_IRQHandler    = Default_Handler
#pragma weak CAN1_SCE_IRQHandler    = Default_Handler
#pragma weak EXTI9_5_IRQHandler        = Default_Handler
#pragma weak TIM1_BRK_IRQHandler    = Default_Handler
#pragma weak TIM1_UP_IRQHandler        = Default_Handler
#pragma weak TIM1_TRG_COM_IRQHandler    = Default_Handler
#pragma weak TIM1_CC_IRQHandler        = Default_Handler
#pragma weak TIM2_IRQHandler        = Default_Handler
#pragma weak TIM3_IRQHandler        = Default_Handler
#pragma weak TIM4_IRQHandler        = Default_Handler
#pragma weak I2C1_EV_IRQHandler        = Default_Handler
#pragma weak I2C1_ER_IRQHandler        = Default_Handler
#pragma weak I2C2_EV_IRQHandler        = Default_Handler
#pragma weak I2C2_ER_IRQHandler        = Default_Handler
#pragma weak SPI1_IRQHandler        = Default_Handler
#pragma weak SPI2_IRQHandler        = Default_Handler
#pragma weak USART1_IRQHandler        = Default_Handler
#pragma weak USART2_IRQHandler        = Default_Handler
#pragma weak USART3_IRQHandler        = Default_Handler
#pragma weak EXTI15_10_IRQHandler    = Default_Handler
#pragma weak RTCAlarm_IRQHandler    = Default_Handler
#pragma weak USBWakeUp_IRQHandler    = Default_Handler
#pragma weak TIM8_BRK_IRQHandler    = Default_Handler
#pragma weak TIM8_UP_IRQHandler        = Default_Handler
#pragma weak TIM8_TRG_COM_IRQHandler    = Default_Handler
#pragma weak TIM8_CC_IRQHandler        = Default_Handler
#pragma weak ADC3_IRQHandler        = Default_Handler
#pragma weak FSMC_IRQHandler        = Default_Handler
#pragma weak SDIO_IRQHandler        = Default_Handler
#pragma weak TIM5_IRQHandler        = Default_Handler
#pragma weak SPI3_IRQHandler        = Default_Handler
#pragma weak UART4_IRQHandler        = Default_Handler
#pragma weak UART5_IRQHandler        = Default_Handler
#pragma weak TIM6_IRQHandler        = Default_Handler
#pragma weak TIM7_IRQHandler        = Default_Handler
#pragma weak DMA2_Channel1_IRQHandler    = Default_Handler
#pragma weak DMA2_Channel2_IRQHandler    = Default_Handler
#pragma weak DMA2_Channel3_IRQHandler    = Default_Handler
#pragma weak DMA2_Channel4_5_IRQHandler    = Default_Handler

void Default_Handler(void) {
    while (1) {}
}
Этот файл я назвал startup.c и включил в состав проекта. Большую часть этого файла занимает определение векторов прерываний. Они специально объявляются с помощью "#pragma weak", чтобы вместо них потом могли быть вставлены адреса фактических функций обработки прерываний. Изначально все вектора прерываний указывают на Default_Handler(), куда попадает программа в случае неправильно оформленной обработки прерывания.
После сброса или подачи питания работа начинается с Reset_Handler(), который сначала инициализирует области данных, вызывает статические конструкторы, если они есть, затем заносит в регистр  SCB->VTOR адрес начала таблицы векторов - без этого у меня программа упорно не хотела работать с прерываниями, и именно по этой причине я отказался от стандартного стартового файла на ассемблере, потому что он не настраивал этот регистр.
Далее вызывается функция SystemInit(), которая настраивает тактовые частоты и параметры всей системы.
И только после этого управление передается функции main().
Программа не будет работать без еще одного важного файла: командного файла для компоновщика, который я взял готовым, только подправил фактические параметры памяти.


Отладка


Для отладки нужно сделать следующие настройки проекта: перейти на пункт "Project->Properties...->Debugger". Здесь можно сделать настройки отладчика как для всего проекта, так и для отдельных целей сборки, я сделал для всего проекта.
Для этого в окне "Select target:" нужно выбрать "". На вкладке "Remote connection" выбрать тип "TCP", "IP address:" = localhost, "Port:" = 3333. 
Перейти на вкладку "Additional GDB commands" и в поле "After connection:" вставить строки:
monitor reset halt
break main
continue
 После этого все готово для отладки. Нужно запустить openocd, а после этого можно запускать отладку: "Debugger->Start".
Для отладчика есть полезные глобальные настройки на "Settings->Compiler and debugger settings->Debugger settings". Например, такой полезный параметр: "Evaluate expression under cursor".