uncleeugene.net

STM32VLDISCOVERY. The грабли.

Просмотров: 6527Комментарии: 0
СтатьиЭлектроника

Когда-то я написал эту статью для we.easyelectronics.ru. Так что сейчас, в рамках “собирания в кучу” всей своей писанины, я просто скопировал её сюда, слегка подредактировав. Оригинал статьи с обсуждением доступен здесь.

(Файлы, прилагаемые к статье: exp-coocox.zip, exp-Keil.zip

Предыстория

Пару дней назад случилось со мной счастье — от заморских купцов пришла бандероль. В бандероли обнаружилась отладочная плата STM32VLDISCOVERY, заказанная давеча на ebay. Обошлось счастье примерно в $23, включая доставку в Алматы, заняла оная доставка недели три. К тому времени Keil (4.23) уже стоял под парами, учебный курс с easyelectronics.ru был прочитан, в общем я был готов мигать светодиодиком, даже двумя. Но не тут-то было!

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

Отладочная плата

На борту установлены:
  • Контроллер STM32F100RBT6 (128KB Flash, 8KB RAM, up to 24 MHz, подробно здесь).
  • Кварц 8MHz, установлен на цангах, так что можно ставить свой.
  • Кварц 32.768KHz. Можно часики мутить ))
  • Программатор-отладчик ST-Link, с выходом SWD, так что можно его использовать и для прошивки своих девайсов. Что характерно, отладчик выполнен на более серьёзном STM32F103CBT6.
  • Две кнопки (правда одна из них — RESET).
  • Два светодиода.
  • Все ноги контроллера на штырьках (или не все, но большинство).
  • Всё необходимое для питания от 5V, 3.3V и USB.

Что интересного на плате: две перемычки CN3, в верхней части, справа от F103CBT6. Если они замкнуты, то отладчик работает с целевым контроллером на плате; если их разомкнуть, то можно подключать внешний контроллер к разъёму SWD (тут же, только слева).

Перемычка “ldd”, слева от целевого контроллера установлена в разрыв питания этого контроллера. Cняв её можно амперметром посмотреть потребление.

На нижней стороне платы есть несколько штук запаиваемых перемычек (как они правильно называются по русски?), о них подробно сказано в мануале. Отмечу здесь наиболее понятные мне сейчас:

SB12, SB13, если их замкнуть (и убрать R15), то кварцем X2 пользоваться не получится, но зато появятся две лишние ноги — PD1 и PD0. С завода они заведены на кварц.

SB14, SB15 — если замкнуть эти две (и убрать R14), то не получится пользоваться часовым кварцем X3, но тоже появятся две ноги — PC15 и PC14.

Также, несколько перемычек отвечают за bootloader (вернее за ноги BOOT), за SWD, и ещё за какую-то ересь. Все подробности есть в мануале, мне же пока сказать нечего, поскольку для мигания диодиком с ними возиться не пришлось. С описанными тоже не пришлось, но там как-то всё понятно ))

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

Заруба

Итак, платка подключена к компу посредством шнурка USB, демо-прошивка моргает диодом, Keil запущен, всё вроде готово для быстрого старта. Но быстрого старта не получается.

Во-первых, написанная простецкая программа, даже из одного while(1) {} не компилируется, или компилируется, но не заливается в контроллер, или заливается, но не стартует. Не говоря уже о диодиках.

Во-вторых, после AVR Studio, тутошняя кухня, со всеми этими startup_stm32f10x_md_vl.s и прочими CMSIS’ами кажется непостижимой. Непонятно, зачем всё это надо и откуда начинать разбираться.

Сразу оговорюсь, что знание ассемблера, си и принципов компиляции исходников у меня так себе (хобби всё же), поэтому прошу гуру прочесть и поправить, если я где-то заблуждаюсь.

Прошивка и отладка

Я так понял это глюк Keil’а: прошить STM32VLDISCOVERY через меню Flash-Download не получается (Keil 4.23). Вот что надо сделать, чтобы прошить контроллер:

  • Alt-F7, вкладка Debug.
  • Ставим Use ST-Link Debugger, кнопка Settings — SWD.
  • Ставим галку Run to main(), чтобы отладка пошла с main().
  • Вкладка Utilities, Use Target Driver for Flash Programming — выбираем St-link. Кнопка Settings тут не работает почему-то.
  • Поставить галку Update Target Before Debugging

Теперь нужно скомпилировать код (F7), и начать отладку (Ctrl-F5). Код окажется в контроллере. Только так. Ну или пользоваться для прошивки отдельным софтом.

Добавляет радости тот факт, что отладка на симуляторе не запускается. Error 65: access violation…: no ‘read’ permission. Лечится выбором другого камня, например с F101RB или F103RB симулятор заводится. Но это маленько неудобно.

Инициализация

Первый вопрос, который у меня возник — откуда же стартует код. Мне по наивности казалось, что как всегда, с int main(), а начальная инициализация — забота компилятора.

А вот и нет! Всё начинается в файла startup_stm32f10x_md_vl.s. Этот файл генерируется Keil’ом при создании проекта. Тут и таблица векторов прерываний, и инициализация стека и кусочки кода, загружающие процедуры обработки прерываний. Нас интересует один кусок:

startup_stm32f10x_md_vl.s
; Reset handler Reset_Handler    PROC                 EXPORT  Reset_Handler             [WEAK]     IMPORT  __main     IMPORT  SystemInit                 LDR     R0, =SystemInit                 BLX     R0                 LDR     R0, =__main                 BX      R0                 ENDP

Это обработчик сброса, то есть код, который выполняется, после старта или reset’а. Насколько я понимаю, он загружает адреса двух процедур и по очереди передаёт им управление. Вторая из этих процедур — родная int main(), а первая — некая SystemInit() (в одной из статей читал, что Keil не вставляет вызов SystemInit() в генерируемый startup-файл. Возможно это зависит от конкретного камня или от версии Keil’а. У меня вставляет).

SystemInit() нашлась в файле system_stm32f10x.c, входящем в состав CMSIS. Отсюда первый вывод: хрен что заработает, пока вы не подключите CMSIS, или не напишите SystemInit() самостоятельно. Я не о том, что надо инициализировать контроллер вообще, а о том, что должна быть такая процедура. Если её нету, то исходник откажется компилироваться.

О том, что такое CMSIS подробно написано в статьях учебного курса ARM, смотрите на главной easyelectronics.ru. Вкратце — это библиотека, которая даёт всем регистрам стандартные понятные имена и здорово облегчает жизнь программерам, в том числе когда надо портировать код с одного камня на другой.

Собственно, SystemInit() отвечает своему названию, она настраивает камень перед стартом основной программы. Процедура украшена кучей дефайнов на все случаи жизни, если её почистить от всего лишнего (для конкретного проекта), то останется примерно следующее:

system_stm32f10x.c
#define SYSCLK_FREQ_24MHz  24000000 uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz; static void SetSysClockTo24(void); void SystemInit (void) {  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */  /* Set HSION bit */  RCC->CR |= (uint32_t)0x00000001;  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */  RCC->CFGR &= (uint32_t)0xF8FF0000;  /* Reset HSEON, CSSON and PLLON bits */  RCC->CR &= (uint32_t)0xFEF6FFFF;  /* Reset HSEBYP bit */  RCC->CR &= (uint32_t)0xFFFBFFFF;  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */  RCC->CFGR &= (uint32_t)0xFF80FFFF;  /* Disable all interrupts and clear pending bits  */  RCC->CIR = 0x009F0000;  /* Reset CFGR2 register */  RCC->CFGR2 = 0x00000000;  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */  /* Configure the Flash Latency cycles and enable prefetch buffer */  SetSysClock(); } static void SetSysClock(void) {  SetSysClockTo24(); } static void SetSysClockTo24(void) {  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/      /* Enable HSE */      RCC->CR |= ((uint32_t)RCC_CR_HSEON);  /* Wait till HSE is ready and if Time out is reached exit */  do  {    HSEStatus = RCC->CR & RCC_CR_HSERDY;    StartUpCounter++;    } while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));  if ((RCC->CR & RCC_CR_HSERDY) != RESET)  {    HSEStatus = (uint32_t)0x01;  }  else  {    HSEStatus = (uint32_t)0x00;  }  if (HSEStatus == (uint32_t)0x01)  {    /* Enable Prefetch Buffer */    FLASH->ACR |= FLASH_ACR_PRFTBE;    /* Flash 0 wait state */    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;        /* HCLK = SYSCLK */    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;    /* PCLK2 = HCLK */    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;    /* PCLK1 = HCLK */    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;    /*  PLL configuration:  = (HSE / 2) * 6 = 24 MHz */    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1_Div2 | RCC_CFGR_PLLMULL6);    /* Enable PLL */    RCC->CR |= RCC_CR_PLLON;    /* Wait till PLL is ready */    while((RCC->CR & RCC_CR_PLLRDY) == 0)    {    }    /* Select PLL as system clock source */    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;        /* Wait till PLL is used as system clock source */    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)    {    }  }  else  { /* If HSE fails to start-up, the application will have wrong clock         configuration. User can add here some code to deal with this error */  } }

Понятно, тут инициализация тактового генератора, без которой жить совсем никак нельзя. Это тот минимум, который нужно сделать самому, если захочется накалякать свою SystemInit(). Естественно, перед сочинением своей SystemInit() необходимо хорошенько раскурить Reference Manual по камню, чтобы иметь представление о его работе.

stm32f10x.h

Вот helloworld, который я написал:

main.c
#include "stm32f10x.h" void InitLeds(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; //Включили тактирование порта GPIOC. GPIOC->CRH &= ~GPIO_CRH_CNF8; // Настроили ногу 8 (синий LED) на выход Push-Pull. GPIOC->CRH &= ~GPIO_CRH_CNF9; // Настроили ногу 9 (зелёный LED) на выход Push-Pull. GPIOC->CRH   |= GPIO_CRH_MODE8_0; // Настроили ногу 8 (синий LED) на 10MHz. GPIOC->CRH   |= GPIO_CRH_MODE9_0; // Настроили ногу 9 (зелёный LED) на 10MHz. return; } int main(void) { uint32_t i; InitLeds(); // Инициализировали выходы на светодиоды. while (1) // Навеки { GPIOC->BRR = GPIO_BRR_BR8; // Погасили синий GPIOC->BSRR = GPIO_BSRR_BS9; // Зажгли зелёный for (i = 0; i < 4000000; i++) ; // Подождали полсекунды GPIOC->BRR = GPIO_BRR_BR9; // Погасили зелёный GPIOC->BSRR = GPIO_BSRR_BS8; // Зажгли синий for (i = 0; i < 4000000; i++) ; // Подождали полсекунды } }

Всё по инструкции: новый проект, добавляю CMSIS, прописываю пути к заголовочным файлам, пишу код, компилирую, 0 errors, 0 warnings, заливаю — не работает. Компилирую пример от ST, заливаю — работает. Снова свой — снова нет. Три раза переделывал, IAR чуть не поставил, материал для этой статьи накопил )) — не работает!

Все ответы оказались в stm32f10x.h и в настройках компилятора. Вот фрагмент:

stm32f10x.h
/* Uncomment the line below according to the target STM32 device used in your application */ #if !defined (STM32F10X_LD) && defined (STM32F10X_LD_VL) && !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL) amp;& !defined (STM32F10X_HD) && !defined (STM32F10X_XL) && !defined (STM32F10X_CL) /* #define STM32F10X_LD */     /*!< STM32F10X_LD: STM32 Low density devices */ /* #define STM32F10X_LD_VL */  /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */ /* #define STM32F10X_MD */     /*!< STM32F10X_MD: STM32 Medium density devices */ /* #define STM32F10X_MD_VL */  /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */ /* #define STM32F10X_HD */     /*!< STM32F10X_HD: STM32 High density devices */ #define STM32F10X_XL      /*!< STM32F10X_XL: STM32 XL-density devices */ /* #define STM32F10X_CL */     /*!< STM32F10X_CL: STM32 Connectivity line devices */ #endif /*  Tip: To avoid modifying this file each time you need to switch between these        devices, you can define the device in your toolchain compiler preprocessor.

По умолчанию здесь выбран STM32F10X_XL, это камни 101, 102 и 103 серий. А на VLDISCOVERY стоит F100, у которого, в частности, максимальная частота поменьше. Для F100 надо выбрать STM32F10X_MD_VL, иначе SystemInit() попытается завести тактовый генератор на невозможных для этого камня 72 мегагерцах.

Меня сбило с толку то, что в примерах от ST тоже была раскомментирована строчка с STM32F10X_XL, но всё работало. Я решил, что это не критично. B только потом обратил внимание на последний комментарий и догадался заглянуть в дефайны препроцессора ST’шных примеров (Alt-F7, вкладка C/C++), чтобы найти там STM32F10X_MD_VL. Из всего этого второй вывод: без чтения и понимания всех исходников ничего не заведётся, разве что случайно. А файл stm32f10x.h вдвойне обязательно надо внимательно просмотреть.

Upd: О кокосе

По совету Ura_X500 скачал и поставил CooCox CoIDE, или просто «Кокос». Самое первое и главное: тулчейн, с которым работает кокос лежит тут.

В кокосе, конечно, удобнее подключать библиотеки, есть драйвер FAT32 и даж RTOS. Кокос сам прописывает дефайны препроцессора, в зависимости от выбранного камня и библиотек. Непонятно, почему Keil этого не может. Кокос сам создаёт main.c )) Это плюсы. Минусов не заметил. Всё что мне не нравится — дело привычки и личных предпочтений. Для себя решил так, буду под Keil'ом, пока не упрусь в 32KB, чего возможно никогда не случится )) Впрочем не буду загадывать…

Но вот интересная штука: решил повторить тот же самый helloworld в кокосе. Создал проект, добавил всё что надо, скопипастил main.c, скомпилировал. Залил. Работает. Но в два раза медленнее! Выкинул из исходников всё лишнее, скопипастил все файлы, совпадают до байта, единственная разница — startup_stm32f10x_md_vl.s (и .c), они сильно заточены под конкретную среду. И stdint.h у каждого свой, но типы вроде совпадают. Оптимизация одинаковая. И всё равно, прошивка собранная в Keil'е работает в два раза быстрее. В чём разница, в компиляторах, в каких-то тонкостях исходников? Я ума не приложу. На случай, если кому интересно, приаттачиваю оба проекта.

Upd2: Разобрался с разницей! Оказалось, что просто у gcc реализация for сильно длиннее — 10 команд, против 6 у Keil'овского компилятора.

Keil:
0x08000346 2200      MOVS     r2,#0x00 0x08000348 E000      B        0x0800034C 0x0800034A 1C52      ADDS     r2,r2,#1 0x0800034C 4803      LDR      r0,[pc,#12]  ; @0x0800035C 0x0800034E 4282      CMP      r2,r0 0x08000350 D3FB      BCC      0x0800034A
gcc:
0x080002da:   mov.w r3, #0 0x080002de:   str r3, [r7, #4] 0x080002e0:   b.n 0x80002ea <main+98> 0x080002e2:   ldr r3, [r7, #4] 0x080002e4:   add.w r3, r3, #1 0x080002e8:   str r3, [r7, #4] 0x080002ea:   ldr r2, [r7, #4] 0x080002ec:   ldr r3, [pc, #52]        ; (0x8000324 <main+156>) 0x080002ee:   cmp r2, r3 0x080002f0:   bls.n 0x80002e2 <main+90>

Оптимизация отключена. При включенной оптимизации поведение не меняется, но понять дизассемблер сложнее. Дебаг удобнее у Keil'а. Кокосовский сильно тормознее, и трудно соотнести исходный листинг с дизассемблером, особенно при -O3. Вообще какая-то другая программа у gcc получается )) Keil тоже позволяет себе вольности, но там понятнее что к чему. А вот минус Keil'а в том, что он почему-то не показывает все регистры при отладке. RCC_CFGR2 я посмотреть не смог, нету его.

А в целом кокос вполне себе неплох ))

Оставьте комментарий!

grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question


Комментарий будет опубликован после проверки

     

  

Loginza Facebook.

(обязательно)