Programming microcontrollers
7 March 2012

STM32F1xx — Инструменты разработчика и FreeRTOS

Добрый день, уважаемые хабровчане. В своих прошлых статьях (STM32F1xx — лечимся от ардуинозависимости вместе, STM32F1хх — продолжаем лечение от ардуинозависимости при помощи LCD) я постарался осветить вопросы перехода с 8-битных микроконтроллеров на новые 32-битные STM32F1xx.
В процессе работы с ними, я, разумеется выбирал инструменты себе «по руке» — то есть, старался найти наиболее удобные для меня отладочные платы, программаторы, IDE. В этой статье я хочу поделиться с вами несколькими соображениями на этот счет, а также описать процесс сборки в выбранной IDE операционной системы реального времени FreeRTOS.

Железо


Традиционно начнем с железа.
В прошлых статьях ядром системы была плата STM32VLDISCOVERY.
Плата, безусловно, хороша, и подкупает тем, что ее цена всего 300 рублей. В принципе, хороший инструмент для того, чтобы начать знакомиться с этим семейством микроконтроллеров. Но.
Дело в том, что при выборе отладочной платы, всегда хочется соблюсти баланс количества и качества, то есть с одной стороны, иметь все необходимое для работы, с другой – не хочется чтобы плата превращалась в огромного и дорогого монстра. У STM32VLDISCOVERY баланс смещен в сторону дешевизны и минимализма.
Полазив по e-bay, я нашел для себя более удобную, на мой взгляд, плату, которую и представляю вашему вниманию. Вот она:
Mini-STM32

image

За 46 долларов нам предлагают:
  1. Микроконтроллер STM32F103VE, имеющий на борту 512 килобайт флеша и 64 килобайта RAM, USB, SDIO (то есть с карточки читать будет намного быстрее, чем по SPI). Кроме того, так как на плате установлена 100-ногая его версия, у него хватает внешних пинов для управления памятью через FSMC. FSMC – это Flexible Static Memory Controller, очень удобный контроллер статической памяти, начиная с, собственно, SRAM и заканчивая NAND флешками. Настроив его и подключив память к управляющим пинам, мы получаем нашу память, мапированную на адресное пространство контроллера. То есть, с этого момента, все взаимодействия с ней будут для нас прозрачны и эквивалентны простой записи в RAM.
  2. Цветной TFT-дисплей с разрешением 320х240 и предустановленным резистивным тач-скрином. Дисплей ставится на плату в виде модуля, при желании, можно отвинтить стоечки, к которым он крепится, и использовать плату без него. В дисплейный модуль, помимо дисплея входит еще и повышающий преобразователь для питания его подсветки, а также контроллер тач-скрина, который управляется по SPI. Кроме того, разъем подключен к упомянутому выше FSMC, что делает взаимодействие с ним в разы удобнее.
    Для примера – вот так выглядит запись в регистры дисплея, а после – в его память, с использованием DMA:
    #define LCDRegister 					(*((volatile u16*) 0x60000000))
    #define LCDMemory 					(*((volatile u16*) 0x60020000))
    //…
    
    void LCDWriteRegister(unsigned short reg, unsigned short data)
    {
    	LCDRegister=reg;
    	LCDMemory=data;
    }
    
    void LCDBeginRAMWrite()
    {
    	LCDRegister=CTR_WRITE_DATA;
    }
    
    int main(void)
    {
    //…
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    	DMA_InitTypeDef DMA_InitStructure;
    	DMA_DeInit(DMA1_Channel1);
    	
    	//Адрес буфера-источника графики
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) PlasmaBuffer1;	
    
    	//Адрес «точки мапирования»  дисплейной памяти
    	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&LCDMemory);		
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    	DMA_InitStructure.DMA_BufferSize = PLASMA_WIDTH*PLASMA_HEIGHT;
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    //…
    	//Собственно, вывод
    	LCDBeginRAMWrite();
    	DMA_SetCurrDataCounter(DMA1_Channel1, PLASMA_WIDTH*PLASMA_HEIGHT);
    	DMA_Cmd(DMA1_Channel1, ENABLE);
    }
    

    После выполнения последней строчки, управление возвращается программе, так что можно сразу же начинать просчет следующего кадра, пока первый выводится через DMA.
  3. Слот micro-SD, подключенный к управляющим пинам SDIO-контроллера STMки. Ситуация та же, что и с дисплеем – в нашем распоряжении быстрый и удобный способ общаться с карточкой памяти, намного быстрее, чем SPI.
  4. USB-разъем и сопутствующая схематика. Как известно, USB-хост определяет наличие устройства на шине по подтягивающему резистору. Соответственно, чтобы иметь возможность сначала совершить какие-либо действия и только потом дать сигнал хосту «я подключен!» нужно подсоединить подтягивающий резистор через транзисторный ключ
    На плате это уже сделано за нас, управляющий транзистор подключен к одному из GPIO-пинов, подтягивающие резисторы настроены на Full-Speed USB.
  5. Неиспользованные пины выведены на двухрядный сорокапиновый разъем, второй разъем – дисплейный, третий – разъем под программатор, J-Link совместимый.
  6. Из приятных бонусов – на плате также присутствует разъем для батарейки, питающей RTC, двухметровая SPI-флешка, один управляемый светодиод, одна кнопка, хороший стабилизатор для питания от USB и max232-конвертер, вместе с разъемом для ком-порта.
    Последний, конечно, по моему мнению – самая лишняя часть во всей плате, только занимающая место, но ладно уж, пусть будет.

Кроме того, по отдельной просьбе, за 28 долларов, продавец приложит к плате J-Link-совместимый программатор (а попросту, настоящий клон Segger J-Link, беззастенчиво под него маскирующийся), со шлейфом, полностью совместимым с разъемом на плате.
Подводя итог вышесказанному, я считаю данную плату вещью первой необходимости для человека, который решил начать изучение STM32F1xx микроконтроллеров.

Софт


Теперь то, что касается IDE. Изначально я выбрал Keil uVision, так как когда-то уже работал с ней.
Ну, что я могу сказать – я проработал в кейле достаточно, и в принципе с ним можно примириться. Но, положа руку на сердце – ИДЕ там ужасна. Также ужасна как и ИДЕ IAR’a, на мой взгляд.
IAR и Keil – признанные лидеры в разработке компиллеров, этого у них не отнять, но я до сих пор не могу понять, почему, имея такие компиллеры, они продолжают тянуть свои IDE, застрявшие по удобству на уровне 2002 года. Как пример могу привести Texas Instruments – у них раньше тоже была своя IDE, в довесок к компиллеру. Потом им это надоело, они взяли Eclipse, допилили его, прикрутили к своим компиллеру и профайлеру, и получили отличный продукт. Почему так не поступят Keil и IAR для меня остается загадкой, но на мой взгляд их IDE не такие удобные, как могли бы быть. Раздражает не очень удобная подсветка синтаксиса, полное отсутствие code-completion’а, не самая удобная навигация по коду. Плюс, uVision частенько у меня падала, но это можно списать на драйвер программатора.
Как бы то ни было, я стал искать альтернативу и нашел ее в виде CooCox IDE.

image

Это бесплатная среда разработки на базе эклипса, которая призвана работать, разумеется, с GCC.
Из плюсов отмечу все достоинства эклипса – удобная навигация, есть автозавершение кода и т.п.
Кроме того прикручен удобный просмотрщик периферии процессора, мне понравился больше чем Кейловский. Очень удобно наличие репозитория компонентов – говоря по-простому, при старте проекта мы как в визарде выбираем нужный нам процессор из списка, после чего отмечаем галочками те из модулей Standard Peripheral Library, которые хотели бы использовать, и они автоматически подключаются к проекту (Об этом чуть подробнее в следующем разделе статьи). Также сразу же можно просмотреть примеры, идущие в комплекте с этим модулем SPL и справку по его функциям.
Минусы CooCox IDE вобрала также из Eclipse, к коим относится тяжеловесность – у меня она потребляет около 180 метров оперативной памяти, занимая на диске 800 мегабайт.
Еще одним минусом является ее работа с этим самым J-Link-ком. Отладка происходит через приложение от создателей J-Link’a, предоставляющее стандартный gdb-шный интерфейс, но почему-то среда при каждом дебаге это приложение перезапускает, в отличие от того же кейла (который вообще работает через свои дллки).
Поэтому старт отладки в Кейле начинается через секунду, в CooCox же – через секунд 20. Возможно, это можно как-нибудь исправить настройками, но я пока таких настроек не видел, поэтому буду благодарен, если кто подскажет.
Тем не менее, я все таки остановился на CooCox — если вас тоже не устраивает IDE от Keil или IAR, или вы не хотите пользоваться ломанным ПО и предпочитаете опенсорс – качайте не задумываясь.

CooCox и FreeRTOS


Об операционной системе FreeRTOS было сказано много, в частности, на хабре (вот, например, одна из статей: FreeRTOS: введение)
Я решил тоже приобщиться к этой технологии и расширить свой арсенал инструментов, тем более, что FreeRTOS не навязывает никакого HAL (Hardware Abstraction Layer, слой абстракции от оборудования, драйверы то бишь), и предоставляет только средства работы с задачами, синхронизацию и меж-процессное взаимодействие, поэтому во многих случаях будет очень удобна.
Рассмотрим поподробнее, что же нам необходимо, чтобы использовать FreeRTOS вместе с CooCox IDE на нашей плате Mini-STM32.
На самом деле, все очень просто. Архитектурное портирование (портирование кода, требуемого шедулером под архитектуру Cortex M3) уже давно выполнено, и нам нужно, по сути, просто правильно составить проект для CooCox.
Начинаем с того, что скачиваем исходники FreeRTOS с их официального сайта.
Вот прямая ссылка: http://sourceforge.net/projects/freertos/files/.
Тем временем создаем новый проект в CooCox, я назвал его FreeRTOS-Mini.



Выбираем в визарде производителя ST, в списке чипов – чип, на котором построена отладочная плата, STM32F103VE.





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



Выбираем там CMSIS Core и CMSIS Boot – это собственно ядро CMSIS и стартовый код, который производит настройку и дергает main()
Кстати, обратите внимание – в CooCox стартовый код написан целиком на C, ни одной асмовой строчки. Лежит в файле cmsis_boot\startup\startup_stm32f10x_hd.c – запомните этот путь, нам нужно будет там кое-что подправить. Заодно добавляем в проект модуль GPIO, чтобы было что поделать в тестовом таске FreeRTOS. Этот модуль автоматом потянет за собой зависимый RCC, отвечающий за настройку клоков.
Теперь возвращаемся к скаченным исходникам FreeRTOS. Для начала скопируем всю папку в папку с нашим проектом. После, начнем удалять лишнее. Итак, лично у меня под нож сразу пошло содержимое папки Demo – там лежат демо-приложения для разных контроллеров, вся папка вам не нужна в любом случае, но при желании можно оставить то, что относится к STM32F103. Единственное, что нам оттуда обязательно понадобится – файл настроек ядра FreeRTOS, который можно взять из любого подходящего проекта, допустим отсюда: Demo\CORTEX_STM32F103_Primer_GCC\FreeRTOSConfig.h
Его можно скопировать в любую папку инклудов, я лично положил в самый корень проекта, рядом с main.c
Далее, в папке source\portable есть множество под-папок, где лежит код, рассчитанный на разные компиллеры и среды. Заходим в папку source\portable\GCC\ARM_CM3, копируем ее двумя уровнями выше, в source\portable. Обращаем внимание на папку source\portable\MemMang – она нам тоже понадобится. Поэтому удаляем все, кроме source\portable\MemMang и свежескопированной source\portable\ARM_CM3
После этого кликаем правой кнопкой в прожект эксплорере CooCox, нажимаем Add Linked Folder и добавляем нашу папку с подготовленными исходниками FreeRTOS. В итоге должно получиться вот такое дерево проекта:



Теперь начинаем править файлы проекта. Начнем с настроек ядра. Оттуда нам нужно будет убрать только пару строчек, связанных со старым проектом – левый, ненужный нам инклуд.

/* Library includes. */
#include "stm32f10x_lib.h"

Все остальное можно оставить без изменений, а можно прочитать статью о настройке ядра FreeRTOS и поменять опции по необходимости, этим мы сейчас заниматься не будем.
Теперь идем в стартап код (cmsis_boot\startup\startup_stm32f10x_hd.c) и делаем следующее: находим строки:

/*----------Function prototypes-----------------------------------------------*/  
extern int main(void);           /*!< The entry point for the application.    */
extern void SystemInit(void);    /*!< Setup the microcontroller system(CMSIS) */
void Default_Reset_Handler(void);   /*!< Default reset handler                */
static void Default_Handler(void);  /*!< Default exception handler            */

У меня это строки 114-122, и добавляем после них такой код:

extern void xPortPendSVHandler( void ) __attribute__ (( naked ));
extern void xPortSysTickHandler( void );
extern void vPortSVCHandler( void ) __attribute__ (( naked ));

Это обработчики прерываний из ядра ОС, которые объявлены в файле port.c. Теперь нам нужно запихнуть их в вектор прерываний, который идет ниже (строки 129-209):

__attribute__ ((section(".isr_vector")))
void (* const g_pfnVectors[])(void) =
{       
  /*----------Core Exceptions-------------------------------------------------*/
  (void *)&pulStack[STACK_SIZE-1],     /*!< The initial stack pointer         */
  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,0,0,0,                      /*!< Reserved                                 */
  vPortSVCHandler,                  /*!< SVCall Handler                           */
  DebugMon_Handler,             /*!< Debug Monitor Handler                    */
  0,                            /*!< Reserved                                 */
  xPortPendSVHandler,               /*!< PendSV Handler                           */
  xPortSysTickHandler,              /*!< SysTick Handler                          */

Соответственно, меняем вектор так, как написано в вышеизложенном коде, заменив строки, отмеченные SVCall Handler, PendSV Handler, SysTick Handler на vPortSVCHandler, xPortPendSVHandler и xPortSysTickHandler соответственно.

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

#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler


После открываем в дереве проекта папку Source\MemMang, и выбираем ту реализацию мемори менеджмента, которая нам подходит. Подробнее об этом написано тут: FreeRTOS Memory Management.
Если коротко – файл номер 1 это упрощенная реализация с выделением памяти, но без освобождения, файл номер 2 – более продвинутая реализация, позволяющая освобождение памяти, и номер 3 – реализация, которая потребует от вас библиотеки с реализованными malloc и free. Я выбрал вторую реализацию, оставшиеся два файла исключаем из компиляции, кликнув правой кнопкой на имя файла в дереве проекта и выбрав пункт Exclude from build.
Осталось совсем чуть-чуть – открываем файл main.c, добавляем туда нужные нам инклуды от SPL:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

Инклуды от FreeRTOS:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

После объявляем функцию, которая будет вызвана до старта шедулера, в соответствии с рекомендациями в таком виде:

static void prvSetupHardware( void );

И функцию, которая будет исполнять роль нашего тестового таска, вот так:

static void prvLedBlink( void *pvParameters );

Реализация функций выглядит так:

void prvSetupHardware()
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void prvLedBlink( void *pvParameters )
{
	GPIO_SetBits(GPIOB,GPIO_Pin_5);
	while(1);
}

В тестовых целях ничего полезного не написал, задача просто зажигает светодиод на плате.
Осталась сама функция main(), которая стартанет задачу и шедулер:

int main(void)
{
	prvSetupHardware();

	xTaskCreate(prvLedBlink,(signed char*)"LED",configMINIMAL_STACK_SIZE,
			NULL, tskIDLE_PRIORITY + 1, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();
    while(1);
}

Вот в принципе и все. Осталось настроить дебаг – для этого жмем «Debug configuration», во вкладке Debugger выбираем наш программатор (J-Link) и порт JTAG.
Ставим галочку Run To Main, чтобы не барахтаться в стартап-коде, в строке GDBServer cmdline tool указываем путь к экзешнику, идущему с программатором (скачать можно с сайта Segger), у меня это C:\SEGGER\JLinkARM_V440b\JLinkGDBServerCL.exe
После жмем Apply и Close.
Теперь компиллим наш проект и жмем на дебаг – если все получилось, после аплода и выполнения, должен загореться светодиод.

Заключение



Правильный выбор инструментов разработчика, безусловно, обеспечит наиболее быстрое и комфортное освоение новых технологий. Я надеюсь, что, осветив в данной статье отладочную плату Mini-STM32 и CooCox IDE, я помог разработчикам приглядеться к новому инструменту. Что касается операционной системы FreeRTOS – это бесспорно очень мощное средство, и, на мой взгляд, хорошая ступень, для перехода от программирования прошивок «в лоб» к использованию эмбеддед операционных систем.

Ссылки


Страничка eBay где можно купить отладочную плату
Официальный сайт CooCox
Официальный сайт FreeRTOS
Русский мануал по FreeRTOS

+25
82k 256
Comments 9
Top of the day