Стандартні бібліотеки периферії. Знову туди: USB із подвійною буферизацією. Використання сніпетів у власних розробках

При створенні першої програми на мікроконтролері STM32 можна йти кількома шляхами. Перший, класичний, беремо точний опис контролера на сайті www.st.com, який фігурує під назвою «Reference Manual» та читаємо опис регістрів периферії. Далі пробуємо записувати їх і дивимося, як працює периферія. Читання цього документа дуже корисно, але на першому етапі освоєння мікроконтролера від цього можна відмовитись, як це не дивно. Інженери STMicroelectronics написали бібліотеку драйверів стандартної периферії. Більш того, вони написали безліч прикладів використання даних драйверів, що може звести програмування вашої програми до натискання клавіш Ctrl + C і Ctrl + V, з наступною невеликою правкою прикладу використання драйвера під свої потреби. Таким чином, підключення бібліотеки драйверів периферії до вашого проекту є другим методом побудови програми. Окрім швидкості написання є й інші переваги цього методу: універсальність коду та застосування інших фірмових бібліотек, таких як USB, Ethernet, керування приводом і т.д., які надаються у вихідних джерелах та використовують стандартний драйвер периферії. Недоліки даного методу теж є: Там де можна обійтися одним рядком коду стандартний драйвер периферії STM32 напише 10. Сама бібліотека периферії також надається у вигляді вихідних файлів, так що можна простежити, який біт якого регістру змінює та чи інша функція. За бажання можна буде перейти від другого методу написання програми до першого, закоментувавши частину коду, який використовує стандартну бібліотеку на свою керуючу регістром периферії. В результаті такої дії ви виграєте у швидкості керування, об'ємі ОЗП та ПЗП, а втратите в універсальності коду. У будь-якому випадку інженери компанії "Промелектроніка" рекомендують використовувати бібліотеку стандартної периферії хоча б на першому етапі.

Найбільші труднощі чекають на розробника при підключенні бібліотеки до свого проекту. Якщо не знати, як це робити, можна витратити багато часу на цей захід, що суперечить ідеї використання готового драйвера. Матеріал присвячений підключенню стандартної бібліотеки до будь-якої родини STM32.

Кожна родина STM32 має свою бібліотеку стандартної периферії. Це з тим, що сама периферія різна. Наприклад, периферія контролерів STM32L як одне із завдань має функцію енергозбереження, що тягне у себе додавання функцій управління. Класичним прикладом, можна вважати АЦП, який у STM32L має можливість апаратного відключення, за тривалої відсутності команди перетворення – один із наслідків завдання енергозбереження. АЦП контролерів сімейств STM32F немає такої функції. Власне кажучи, через наявність апаратної різниці периферії маємо різні бібліотеки драйверів. Крім очевидної різниці функцій контролера має місце поліпшення периферії. Так, периферія контролерів сімейств, які були випущені пізніше, може бути більш продуманою та зручною. Наприклад, периферія контролерів STM32F1 та STM32F2 має відмінності з управління. На думку автора управління периферією STM32F2 зручніше. І це зрозуміло чому: STM32F2 сімейство було випущено пізніше, і це дозволило розробникам врахувати деякі нюанси. Відповідно, для цих сімейств – індивідуальні бібліотеки управління периферією. Ідея вищесказаного проста: на сторінці мікроконтролера, який ви збираєтеся використовувати, знаходиться відповідна бібліотека периферії.

Незважаючи на відмінність периферії у сімействах драйвери приховують 90% відмінностей у собі. Наприклад, функція налаштування, згаданого вище АЦП для всіх сімейств виглядає однаково:

void ADC_Init(ADC_Nom, ADC_Param),

де ADC_Nom – номер АЦП як ADC1, ADC2, ADC3 тощо.

ADC_Param – покажчик структури даних, яким чином треба налаштувати АЦП (від чого запускатися, скільки каналів оцифровувати, чи це циклічно виконувати і т.д.)

10% відмінностей сімейств, у цьому прикладі, які доведеться виправити при переході з одного сімейства STM32 на інше, заховані в структурі ADC_Param. Залежно від сімейства кількість полів цієї структури може бути різною. Загальна частина, має однаковий синтаксис. Таким чином, переклад програми для одного сімейства STM32, написаного на базі стандартних бібліотек периферії на інше, дуже простий. У частині універсалізації рішень на мікроконтролерах STMicroelectronics неперевершений!

Отже, ми завантажили бібліотеку для STM32. Що далі? Далі нам потрібно створити проект і підключити до нього необхідні файли. Створення проекту розглянемо з прикладу середовища розробки IAR Embedded Workbench. Запускаємо середовище розробки та заходимо на вкладку “Project”, вибираємо пункт створення проекту “Create project”:

У новому проекті, що з'явився, вводимо налаштування, навівши курсор на назву проекту, натиснувши праву клавішу миші і вибравши в меню «Options»:

Області пам'яті ОЗП та ПЗП:

При натисканні кнопки Save середа запропонує записати новий файлопис контролера в папку проекту. Автор рекомендує створювати кожному проекту індивідуальний файл *.icp та зберігати його у папці з проектом.

Якщо ви збираєтеся внутрішньосхемно налагоджувати свій проект, що рекомендується, то вводимо тип відладчика, що застосовується:

На вкладці вибраного налагоджувача вказуємо інтерфейс підключення відладчика (у нашому випадку вибраний ST-Link) до контролера:



З цього моменту наш проект без бібліотек готовий до компіляції та завантаження у контролер. В інших середовищах, таких як Keil uVision4, Resonance Ride7 тощо, потрібно виконати ці ж дії.

Якщо у файлі main.c написати рядок:

#include "stm32f10x.h" або

#include "stm32f2xx.h" або

#include "stm32f4xx.h" або

#include "stm32l15x.h" або

#include "stm32l10x.h" або

#include "stm32f05x.h"

із зазначенням розташування даного файлу, або копіюванням даного файлу в папку проекту, відбудеться асоціація деяких областей пам'яті з регістрами периферії відповідного сімейства. Сам файл знаходиться в папці бібліотеки стандартної периферії в розділі: \CMSIS\CM3\DeviceSupport\ST\STM32F10x (або схоже за назвою для інших сімейств). З цього моменту ви замінюєте адресу регістра периферії у вигляді числа його назвою. Навіть якщо ви не збираєтеся використовувати функції стандартної бібліотеки, рекомендується зробити таке підключення.

Якщо ви збираєтеся використовувати у своєму проекті переривання, то рекомендується підключити стартовий файл з розширенням *.s, який знаходиться на шляху \CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\iar, або подібним, для інших сімейств. Для кожного середовища файл свій. Відповідно, якщо ми використовуємо IAR EWB, то маємо взяти файл із папки IAR. Це зумовлено невеликою відмінністю синтаксису середовищ. Тому, щоб проект запустився відразу, інженери STMicroelectronics написали кілька варіантів стартових файлів для кількох найпопулярніших середовищ розробки. Більшість сімейств STM32 мають один файл. Сімейство STM32F1 має кілька фалів, що запускають:

  • startup_stm32f10x_cl.s – для мікроконтролерів STM32F105/107
  • startup_stm32f10x_xl.s - для мікроконтролерів STM32F101/STM32F103 768кб і більше
  • startup_stm32f10x_hd.s - для мікроконтролерів STM32F101/STM32F103 з пам'яттю Flash 256-512 кб
  • startup_stm32f10x_md.s - для мікроконтролерів STM32F101/STM32F102/STM32F103 з пам'яттю Flash 64-128 кб
  • startup_stm32f10x_ld.s - для мікроконтролерів STM32F101/STM32F102/STM32F103 з пам'яттю Flash менше 64кб
  • startup_stm32f10x_hd_vl.s для мікроконтролерів STM32F100 з пам'яттю Flash 256-512 кб
  • startup_stm32f10x_md_vl.s для мікроконтролерів STM32F100 з пам'яттю Flash 64-128 кб
  • startup_stm32f10x_ld_vl.s для мікроконтролерів STM32F100 з пам'яттю Flash 32кб і менше

Отже, залежно сімейства, підродини та середовища розробки додаємо файл, що запускає, в проект:

Саме тут виявляється мікроконтролер під час старту програми. Переривання послідовно викликає функцію SystemInit(), потім __iar_program_start. Друга функція обнулює або записує заздалегідь задані значенняглобальних змінних, після чого здійснює перехід у програму користувача main(). Функція SystemInit() налаштовує тактування мікроконтролера. Саме вона дає відповіді на запитання:

  • Чи потрібно перемикатися на зовнішній кварц (HSE)?
  • Як помножити частоту HSI/HSE?
  • Чи потрібне підключення черги завантаження команд?
  • Яка потрібна затримка при завантаженні команди (через низьку швидкість роботи Flash пам'яті)
  • Як поділити тактування шин периферії?
  • Чи потрібно розмістити код у зовнішній ОЗП?

Функцію SystemInit() можна прописати вручну у проекті. Якщо оформити цю функцію як порожню, контролер буде працювати на внутрішньому RC-генераторі з частотою порядку 8МГц (залежно від типу сімейства). Варіант 2 – підключити до проекту файл system_stm32f10x.c (або схожий за назвою залежно від типу сімейства, що використовується), який розташований у бібліотеці по дорозі: Libraries CMSIS CM3 DeviceSupport ST MST22F10x. Цей файл має функцію SystemInit(). Зверніть увагу на частоту зовнішнього кварцу HSE_VALUE. Цей параметр виставляється в заголовному файлі stm32f10x.h. Стандартне значення 8 та 25МГц, залежно від сімейства STM32. Основне завдання функції SystemInit() – переключити тактування на зовнішній кварц та помножити певним чином цю частоту. Що станеться, якщо значення HSE_VALUE вказано 8МГц, ядро ​​повинне тактуватися частотою 72МГц, а за фактом на платі стоїть кварц 16МГц? Внаслідок таких некоректних дій ядро ​​отримає тактування 144МГц, які можуть опинитися за межами гарантованої роботи системи на STM32. Тобто. при підключенні файлу system_stm32f10x.c потрібно вказати значення HSE_VALUE. Все це означає, що файли system_stm32f10x.c, system_stm32f10x.h та stm32f10x.h (або схожі за назвою для інших сімейств) мають бути індивідуальними для кожного проекту. І

Інженери STMicroelectronics створили інструмент Clock Configuration Tool, який дозволяє правильно налаштувати тактування системи. Це файл Excel, що генерує файл system_stm32xxx.c (схожий за назвою для заданого сімейства сімейств) після завдання вхідних і вихідних параметрів системи. Розглянемо його роботу з прикладу STM32F4 сімейства.

Варіанти: внутрішній RC-генератор, внутрішній RC-генератор з множенням частоти або зовнішній кварц з множенням частоти. Після вибору джерела тактування вводимо параметри бажаної конфігурації системи, такі як вхідну частоту (при використанні зовнішнього кварцу), частоту тактування ядра, дільники тактування шин периферії, роботу буфера вибірки команд та інші. Натиснувши кнопку “Generate”, отримуємо вікно


Підключення файлу system_stm32f4xx.c та його аналогів вимагатиме підключення ще одного файлу стандартної бібліотеки периферії. Для керування тактуванням є цілий набір функцій, що викликаються із файлу system_stm32xxxxxx.c. Ці функції розташовані у файлі stm32f10x_rcc.c та його заголовку. Відповідно, підключаючи до проекту файл файлу system_stm32xxxxxx.c, потрібно підключити stm32f10x_rcc.c, інакше лінкер середовища повідомить про відсутність опису функцій з ім'ям RCC_xxxxxxx. Зазначений файл знаходиться в бібліотеці периферії шляхом: Libraries\STM32F10x_StdPeriph_Driver\src, а його заголовок \Libraries\STM32F10x_StdPeriph_Driver\inc.

Підключення заголовних файлів драйвера периферії відбувається у файлі stm32f10x_conf.h, який посилається stm32f10x.h. Файл stm32f10x_conf.h – це просто набір заголовних файлів драйверів конкретних периферій контролера, які підлягають включенню до проекту. Спочатку всі заголовки "#include" позначені як коментарі. Підключення заголовного файлу периферії полягає у знятті коментаря з відповідної назви файлу. У нашому випадку це рядок #include "stm32f10x_rcc.h". Очевидно, що файл stm32f10x_conf.h індивідуальний кожному за проекту, т.к. різні проекти використовують різну периферію.

І останнє. Треба вказати кілька директив препроцесору компілятора та шляхи до заголовних файлів.



Шляхи до заголовних файлів можуть бути іншими, залежно від розташування бібліотеки периферії щодо папки проекту, а наявність USE_STDPERIPH_DRIVER – обов'язково при підключенні драйверів периферії стандартної бібліотеки.

Отже, ми підключили стандартну бібліотеку до проекту. Більш того, ми підключили один із стандартних драйверів периферії до проекту, який виконує управління тактуванням системи.

Ми пізнали, як виглядає пристрій бібліотеки зсередини, тепер кілька слів про те, як вона виглядає зовні.



Таким чином, підключення заголовного файлу stm32f10x.h у додатку тягне за собою підключення інших заголовних файлів та файлів коду. Деякі з наведених на малюнку описані вище. Декілька слів про інші. Файли STM32F10x_PPP.x – це файли драйверів периферії. Приклад підключення такого файлу показано вище, це RCC – периферія керування тактуванням системи. Якщо ми хочемо підключити драйвери іншої периферії, то назва файлів, що підключаються, виходить заміною «PPP» назвою периферії, наприклад АЦП - STM32F10x_ADC.с, або порти вводу-виводу STM32F10x_GPIO.с, або ЦАП - STM32F10x. В цілому інтуїтивно зрозуміло, який файл потрібно підключити при підключенні заданої периферії. Файли "misc.c", "misc.h" - це за великим рахунком ті ж STM32F10x_PPP.x, тільки виконують керування ядром. Наприклад, налаштування векторів переривань, вбудованих в ядро ​​або керування таймером SysTick, який є частиною ядра. Файли xxxxxxx_it.c описують вектор неперерв контролера, що не маскуються. Вони можуть бути доповнені векторами переривань периферії. Файл core_m3.h визначає ядро ​​CortexM3. Дане ядро ​​стандартизоване і може зустрічатися у мікроконтролерах інших виробників. Для крос-платформної універсалізації компанія STMicroelectronics провела роботу зі створення окремої бібліотеки ядра CortexM, після чого компанія ARM стандартизувала її та поширила на інших виробників мікроконтролерів. Отже, перехід на STM32 з контролерів інших виробників з ядром CortexM буде трохи простіше.

Отже, ми можемо підключити бібліотеку стандартної периферії до будь-якої STM32. Того, хто навчився це робити чекає на приз: дуже просте програмування мікроконтролерів. Бібліотека, крім драйверів у вигляді вихідних файлів, містить безліч прикладів застосування периферії. Наприклад, розглянемо створення проекту з участю виходів порівняння таймера. При традиційному підході ми уважно вивчатимемо опис регістрів даної периферії. Але зараз ми можемо вивчити текст програми, що працює. Заходимо до папки прикладів стандартної периферії, яка знаходиться на шляху ProjectSTM32F10x_StdPeriph_Examples. Тут знаходяться папки прикладів з назвою периферії, що застосовується. Заходимо до папки «TIM». Таймери STM32 мають безліч функцій і налаштувань, тому одним прикладом можливості контролера продемонструвати неможливо. Тому всередині цього каталогу є безліч прикладів застосування таймерів. Нас цікавить генерація ШІМ сигналу таймером. Заходимо до папки «7PWM_Output». Всередині є опис роботи програми англійською мовою та набір файлів:

main.c stm32f10x_conf.h stm32f10x_it.h stm32f10x_it.c system_stm32f10x.c

Якщо проект не має переривань, змістовна частина повністю розташована у файлі main.c. Копіюємо ці файли до каталогу проекту. Скомпілювавши проект, ми отримаємо програму для STM32, яка налаштує таймер і порти введення-виведення на генерацію 7-ми ШІМ сигналів від таймера 1. Далі, ми можемо пристосувати вже написаний код під своє завдання. Наприклад, зменшити число ШІМ сигналів, змінити шпаруватість, напрямок рахунку тощо. Функції та їх параметри добре описані у файлі stm32f10x_stdperiph_lib_um.chm. Назви ж функцій та їх параметрів легко асоціюються з призначенням для тих, хто трохи знає англійську мову. Для наочності наводимо частину коду прикладу:

/* Time Base configuration */ TIM_TimeBaseStructure.TIM_Prescaler = 0; // Поділ лічильних імпульсів відсутня (16-бітний регістр) TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // напрямок рахунку вгору TIM_TimeBaseStructure.TIM_Period = TimerPeriod; // Рахунок виконувати до значення TimerPeriod (константа в програмі) TIM_TimeBaseStructure.TIM_ClockDivision = 0; // розподіл лічильних відсутній TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // Лічильник переповнень для генерації подій (не використовується в програмі) TIM_TimeBaseInit(TIM1, & TIM_TimeBaseStructure); // введення значень TimeBaseStructure в регістри таймера 1 (введення даних у цю // змінну - вище) /* Channel 1, 2,3 and 4 Configuration in PWM mode */ // налаштування ШИМ виходів TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // Режим роботи ШИМ2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // дозволити вихід ШИМ сигналів таймера TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // дозволити компліментарний вихід ШІМтаймера TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; // ширина імпульс Channel1Pulse – константа у програмі TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // Налаштування полярності виходу TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; // Налаштування полярності компліментарного виходу TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; // встановлення безпечного стану виходу ШИМ TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; // встановлення безпечного стану компліментарного виходу ШІМ TIM_OC1Init(TIM1, &TIM_OCInitStructure); // Введення значень змінної TIM_OCInitStructure в регістри ШІМ каналу 1 // Таймера1 TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; // змінюємо ширину імпульсу змінної OCInitStructure і вводимо їх у TIM_OC2Init(TIM1, &TIM_OCInitStructure); // Регістри ШІМ каналу 2 таймера1 TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; // змінюємо ширину імпульсу змінної OCInitStructure і вводимо їх у TIM_OC3Init(TIM1, &TIM_OCInitStructure); // Регістри ШІМ каналу 3 таймера1 TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; // змінюємо ширину імпульсу змінної OCInitStructure і вводимо їх у TIM_OC4Init(TIM1, &TIM_OCInitStructure); // Регістри ШІМ каналу 4 таймера1 / * TIM1 counter enable * / TIM_Cmd (TIM1, ENABLE); // запускаємо таймер1 / * TIM1 Main Output Enable * / TIM_CtrlPWMOutputs (TIM1, ENABLE); // Дозволяємо роботу виходів порівняння таймера 1

У правій частині автор залишив коментар російською мовою до кожного рядка програми. Якщо відкрити цей приклад в описі функцій бібліотек stm32f10x_stdperiph_lib_um.chm, то ми побачимо, що всі параметри функцій, що використовуються, мають посилання на власний описде будуть вказані їхні можливі значення. Самі функції теж мають посилання на власний опис та вихідний код. Це дуже корисно, т.к. знаючи, що функція робить, ми можемо простежити, як вона це робить, на які біти регістрів периферії як вона впливає. Це, по-перше, ще одне джерело інформації для освоєння контролера, засноване на практичному використанні контролера. Тобто. ви спочатку вирішите технічне завдання, а потім вивчіть саме рішення. По-друге, це поле для оптимізації програми тому, кого бібліотека не влаштує за швидкістю роботи та обсягом коду.



Коли тільки починаєш програмувати мікроконтролери або давно не займався програмуванням, розбиратися в чужому коді досить не легко. Запитання "Що це таке?" і "Звідки це взялося?" виникають чи не кожному поєднанні букв і цифр. І чим швидше приходить розуміння логіки "що? навіщо? і звідки?", тим легше проходить вивчення чужого коду, в тому числі прикладів. Правда іноді для цього доводиться не один день "пострибати за кодом" та "погортати мануалів".

У всіх мікроконтролерів STM32F4xx досить багато периферії. За кожним периферійним пристроєм мікроконтролерів закріплено певну, конкретну і непереміщувану область пам'яті. Кожна область пам'яті складається з регістрів пам'яті, причому ці регістри можуть бути 8-розрядними, 16-розрядними, 32-розрядними або ще як залежить від мікроконтролера. У мікроконтролері STM32F4 ці регістри 32-розрядні і кожен регістр має своє призначення та свою конкретну адресу. Ніщо не заважає у своїх програмах звертатися безпосередньо до них, вказуючи адресу. За якою адресою розміщений той чи інший регістр і до якого периферійного пристрою він відноситься вказується в картці пам'яті. Для STM32F4 така картка пам'яті є у документі DM00031020.pdf, який можна знайти на сайті st.com. Документ називається

RM0090
Reference manual
STM32F405xx/07xx, STM32F415xx/17xx, STM32F42xxx і STM32F43xxx Advanced ARM-based 32-bit MCUs

В розділі 2.3 Memory mapна сторінці 64 починається таблиця з адресами областей регістрів та їх приналежністю до периферійного пристрою. У тій же таблиці є посилання розділ з більш докладним розподілом пам'яті кожної периферії.

Зліва в таблиці вказаний діапазон адрес, у середині назва периферії та в останньому стовпці - де знаходиться більш докладний опис розподілу пам'яті.

Так для портів введення-виводу загального призначення GPIO у таблиці розподілу пам'яті можна знайти що для них виділені адреси починаючи з 0х4002 0000. Порт введення-виведення загального призначення GPIOA займає діапазон адрес від 0х4002 000 до 0х4002 03FF. Порт GPIOB займає діапазон адрес 0х4002 400 - 0х4002 07FF. І так далі.

Для того щоб переглянути детальніший розподіл у самому діапазоні, потрібно просто пройти за посиланням.

Тут також знаходиться таблиця, але з картою пам'яті для діапазону адрес GPIO. Згідно з цією карткою пам'яті перші 4 байти належать регістру MODER, наступні 4 байти належать регістру OTYPER і так далі. Адреси регістрів вважаються від початку діапазону, що належить конкретному порту GPIO. Тобто кожен регістр GPIO має конкретну адресу, яку можна використовувати для розробки програм для мікроконтролера.

Але використання адрес регістрів для людини незручно і загрожує великою кількістю помилок. Тому виробники мікроконтролерів створюють стандартні бібліотеки, які полегшують роботу з мікроконтролерами. У цих бібліотеках фізичним адресам ставитися у відповідність до них буквене позначення. Для STM32F4xx ці відповідності задані у файлі stm32f4xx.h. Файл stm32f4xx.hналежить бібліотеці CMSISі лежить у папці Libraries\CMSIS\ST\STM32F4xx\Include\.

Подивимося, як визначається в бібліотеках порт GPIOA. Аналогічно визначається і решта. Достатньо зрозуміти принцип. Файл stm32f4xx.hдосить великий і тому краще використовувати пошук або можливості, які надає ваш інструмент.

Для порту GPIOA знаходимо рядок у якому згадується GPIOA_BASE

GPIOA_BASE визначається через AHB1PERIPH_BASE

AHB1PERIPH_BASE у свою чергу визначається через PERIPH_BASE

А у свою чергу PERIPH_BASE визначається як 0х4000 0000. Якщо подивитися карту розподілу пам'яті периферійних пристроїв (у розділі 2.3 Memory mapна сторінці 64), то побачимо цю адресу в самому низу таблиці. З цієї адреси починаються регістри всієї периферії мікроконтролера STM32F4. Тобто PERIPH_BASE - це початкова адреса всієї периферії мікроконтролерів STM32F4xx взагалі, і мікроконтролера STM32F407VG зокрема.

AHB1PERIPH_BASE визначається як сума (PERIPH_BASE + 0x00020000). (Див.картинки назад). Це буде адреса 0х4002 0000. У картці пам'яті з цієї адреси починаються порти введення-виведення загального призначення GPIO.

GPIOA_BASE визначається як (AHB1PERIPH_BASE + 0x0000), тобто це початкова адреса групи регістрів порту GPIOA.

А сам порт GPIOA визначається як структура з регістрів, розміщення яких у пам'яті починається з адреси GPIOA_BASE (див. рядок #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE).

Структура кожного порту GPIO визначається типом GPIO_TypeDef.

Таким чином, стандартні бібліотеки, даному випадкуфайл stm32f4xx.hпросто олюднюють машинну адресацію. Якщо ви побачите запис GPIOA-> ODR = 1234, це означає, що за адресою 0х40020014 буде записано число 1234. GPIOA має початкову адресу 0х40020000 і регістр ODR має адресу 0х14 від початку діапазону, тому GPIOA->04.

Або наприклад, вам не подобається запис GPIOA->ODR, то можна визначити #define GPIOA_ODR ((uint32_t *) 0x40020014)і отримати той же результат, записавши GPIOA_ODR = 1234;. Тільки наскільки це доцільно? Якщо дійсно хочеться ввести свої позначення, краще просто перепризначити стандартні. Як це робиться, можна переглянути у файлі stm32f4_discovery.hНаприклад, ось так там визначається один із світлодіодів:

#define LED4_PIN GPIO_Pin_12
#define LED4_GPIO_PORT GPIOD
#define LED4_GPIO_CLK RCC_AHB1Periph_GPIOD

Більше детальний описпериферії портів перебувати в stm32f4xx_gpio.h

Взаємодія коду користувача з регістрами ядра і периферії мікроконтролерів STM32 може бути здійснена двома способами: за допомогою стандартних бібліотек або за допомогою наборів сніпетів (програмних підказок). Вибір між ними залежить від обсягу власної пам'ятіконтролера, необхідного швидкодії, термін виконання розробки. У статті аналізуються особливості структури, переваги та недоліки наборів сніпетів для мікроконтролерів сімейств STM32F1 та STM32L0 виробництва компанії STMicroelectronics.

Однією з переваг використання мікроконтролерів STMicroelectronics є широкий спектр засобів розробки: документації, налагоджувальних плат, програмного забезпечення.

Програмне забезпечення для STM32 включає власне ПЗ виробництва компанії STMicroelectronics, джерела Open Source, комерційне ПЗ.

ПЗ від STMicroelectronics має важливі переваги. По-перше, воно доступне для безкоштовного скачування. По-друге, програмні бібліотеки представлені у вигляді вихідних кодів – користувач може модифікувати код, враховуючи незначні обмеження, описані в ліцензійній угоді.

Бібліотеки STMicroelectronics відповідають ANSI-C і можна розділити за рівнем абстракції (рисунок 1):

  • CMSIS (Core Peripheral Access Layer) – рівень регістрів ядра та периферії, бібліотека ARM;
  • Hardware Abstraction Layer – низькорівневі бібліотеки: стандартні бібліотеки периферії (standard peripheral library), набори сніпетів (snippets);
  • Middleware – бібліотеки середнього рівня: Операційні системиреального часу (RTOS), файлові системи, USB, TCP/IP, Bluetooth, Display, ZigBee, Touch Sensing та інші;
  • Application Field – бібліотеки прикладного рівня: аудіо, керування двигунами, автомобільні та промислові рішення.

На малюнку 1 видно, що для взаємодії з рівнем CMSIS компанія STMicroelectronics пропонує використовувати два основних інструменти – стандартні бібліотеки та сніпети.

Стандартна бібліотека – це набір драйверів. Кожен драйвер надає користувачеві функції та визначення для роботи з конкретним периферійним блоком (SPI, USART, ADC тощо). Безпосередньо користувач з регістрів рівня CMSIS не взаємодіє.

Набори сніпетів - це високоефективні програмні приклади, що використовують прямий доступ до регістрів CMSIS Розробники ПЗ можуть використовувати реалізації функцій цих прикладів у власному коді.

Кожен із способів має переваги та недоліки. Вибір між ними робиться з урахуванням доступного обсягу FLASH та ОЗП, необхідної швидкодії, терміну виконання розробки, досвідченості програмістів та інших обставин.

Рівень CMSIS

Мікроконтролер - це складна цифро-аналогова мікросхема, що складається з процесорного ядра, пам'яті, периферійні блоки, цифрові шини і так далі. Взаємодія з кожним блоком відбувається за допомогою регістрів.

З погляду програмістів, мікроконтролер є простір пам'яті. У ньому розміщені не тільки ОЗУ, FLASH та EEPROM, а й програмні регістри. Кожному апаратному регістру відповідає осередок пам'яті. Таким чином, щоб записати дані в регістр або віднімати його значення, програмісту необхідно звернутися до відповідного осередку адресного простору.

Людина має деякі особливості сприйняття. Наприклад, символьні назви сприймаються ним набагато краще, ніж адреси осередків пам'яті. Це особливо помітно, коли використовується велика кількість осередків. У мікроконтролерах ARM кількість регістрів, а отже, і осередків, що використовуються, перевищує тисячу. Щоб спростити роботу, необхідно визначити символьні покажчики. Це визначення виконано лише на рівні CMSIS.

Наприклад, щоб встановити стан висновків порту А, потрібно записати дані в регістр GPIOA_ODR. Це можна зробити двома способами – скористатися покажчиком з адресою осередку 0xEBFF FCFF зі зміщенням 0x14 або застосувати покажчик із символьною назвою GPIOA та готову структуру, що визначає зміщення. Очевидно, що другий варіант набагато простіше сприйняття.

CMSIS виконує та інші функції. Він реалізований у вигляді наступної групи файлів:

  • startup_stm32l0xx.s містить асемблерний стартовий код Cortex-M0+ та таблицю векторів переривань. Після виконання стартової ініціалізації відбувається передача управління спочатку функції SystemInit() (нижче наведено пояснення), а потім – основної функції int main(void);
  • stm32l0xx.h містить визначення, необхідні для виконання основних операцій з бітами та визначення типу використовуваного мікропроцесора;
  • system_stm32l0xx.c/.h. Після початкової ініціалізації виконується функція SystemInit(). Вона виробляє первинне налаштуваннясистемної периферії, таймінгів блоку RCC;
  • stm32l0yyxx.h – файли реалізації конкретних мікроконтролерів (наприклад, stm32l051xx.h). Саме в них визначаються символьні покажчики, структури даних, бітові константи та усунення.

Взаємодія з CMSIS. Стандартні бібліотеки та сніпети

Число регістрів для мікроконтролерів STM32 у більшості моделей перевищує тисячу. Якщо використовувати пряме звернення до регістрів, код користувача стане нечитаним і абсолютно непридатним для підтримки і модернізації. Ця проблема може бути вирішена під час використання стандартної бібліотеки периферії (standard peripheral library).

Стандартна бібліотека периферії – набір низькорівневих драйверів. Кожен драйвер надає користувачеві набір функцій роботи з периферійним блоком. Таким чином, користувач використовує функції, а не звертається безпосередньо до регістрів. У цьому рівень CMSIS виявляється прихованим від програміста (рисунок 2а).

Мал. 2. Взаємодія з CMSIS за допомогою стандартної бібліотеки (а) та сніпетів (б)

Наприклад, взаємодія з портами вводу/виводу STM32L0 реалізовано за допомогою драйвера, виконаного у вигляді двох файлів: stm32l0xx_hal_gpio.h і stm32l0xx_hal_gpio.c. У stm32l0xx_hal_gpio.h дано основні визначення типів і функцій, а в stm32l0xx_hal_gpio.c представлено їх реалізацію.

Такий підхід має цілком очевидні переваги (таблиця 1):

  • Швидкість створення коду. Програмістові не потрібно вивчати перелік регістрів. Він одразу починає працювати на вищому рівні. Наприклад, для прямої взаємодії з портом введення/виводу в STM32L0 необхідно знати і вміти працювати з одинадцятьма регістрами управління/стану, більшість з яких мають до 32 бітів, що настроюються. При використанні бібліотечного драйвера достатньо освоїти вісім функцій.
  • Простота та наочність коду. Код користувача не забитий назвами регістрів, може бути прозорим і легко читається, що важливо при роботі команди розробників.
  • Високий рівень абстракції. При використанні стандартної бібліотеки код виявляється досить платформо-незалежним. Наприклад, якщо змінити мікроконтролер STM32L0 на мікроконтролер STM32F0, частина коду, що працює з портами вводу/виводу, взагалі не доведеться змінювати.

Таблиця 1. Порівняння способів реалізації коду користувача

Параметр порівняння При використанні стандартної
бібліотеки периферії
При використанні наборів сніпетів
Розмір коду середній мінімальний
Витрати ОЗУ середні мінімальні
Швидкодія середня максимальне
Читання коду відмінна низька
Рівень незалежності від платформи середній низький
Швидкість створення програм висока низька

Наявність додаткової оболонки у вигляді драйверів має очевидні недоліки (таблиця 1):

  • Збільшення обсягу програмного коду. Реалізовані в бібліотечному коді функції потребують додаткового місця у пам'яті.
  • Підвищені витрати ОЗП за рахунок збільшення числа локальних змінних та використання громіздких структур даних.
  • Зниження швидкодії за рахунок збільшення накладних витрат за виклику бібліотечних функцій.

Саме наявність цих недоліків призводило до того, що користувач часто був змушений оптимізувати код – самостійно реалізовувати функції взаємодії з CMSIS, оптимізувати бібліотечні функції, прибираючи все зайве, копіювати реалізації бібліотечних функцій безпосередньо у свій код, використовувати __INLINE-директиви для збільшення швидкості виконання. В результаті, витрачалося додатковий часна доопрацювання коду.

Компанія STMicroelectronics, йдучи назустріч розробникам, випустила збірники сніпетів STM32SnippetsF0 та STM32SnippetsL0.

Сніппети входять у код користувача (рисунок 2б).

Використання сніпетів надає очевидні переваги:

  • підвищення ефективності та швидкодії коду;
  • зменшення обсягу програми;
  • зниження обсягів використовуваної ОЗП та навантаження на стек.

Втім, варто зазначити й недоліки:

  • зменшення простоти та наочності коду за рахунок «забруднення» його назвами регістрів та самостійною реалізацією низькорівневих функцій;
  • зникнення платформо-незалежності.

Таким чином, вибір між стандартною бібліотекою та сніпетами не є очевидним. Найчастіше варто говорити не про конкуренцію, а про взаємне їх використання. на початкових етапахдля швидкої побудови «красивого» коду, логічно використовувати стандартні драйвери. При необхідності оптимізації можна звернутися до готових сніпетів, щоб не витрачати час на розробку власних оптимальних функцій.

Стандартні бібліотеки драйверів та сніпетів STM32F0 та STM32L0 (таблиця 2) доступні для вільного скачування на сайті www.st.com.

Таблиця 2. Низькорівневі бібліотеки для STM32F10 та STM32L0

Більш тісне знайомство зі СНІПЕТ, як і з будь-яким ПЗ, слід починати з розгляду особливостей ліцензійної угоди.

Ліцензійну угоду

Будь-який відповідальний програміст перед використанням сторонніх програмних продуктівуважно вивчає ліцензійну угоду. Незважаючи на те, що збірники сніпетів виробництва ST Microelectronics не вимагають ліцензування та доступні для вільного скачування, це не означає, що на їх використання не накладаються обмеження.

Ліцензійна угода входить у комплект всіх продуктів, що вільно скачуються, виробництва компанії STMicroelectronics. Після завантаження STM32SnippetsF0 та STM32SnippetsL0 у кореневому каталозі легко виявити документ MCD-ST Liberty SW License Agreement V2.pdf, який знайомить користувача з правилами використання ПЗ.

Папка Project містить підкаталоги з прикладами для конкретних периферійних блоків, готові проекти для ARM Keil і EWARM, а також файли main.c.

Запуск та особливості використання наборів сніпетів STM32SnippetsF0 та STM32SnippetsL0

Особливістю даних наборів СНІП є їх платформозалежність. Вони призначені до роботи з конкретними платами. STM32SnippetsL0 використовує платформу STM32L053 Discovery board, а STM32SnippetsF0 – плату STM32F072 Discovery board.

При використанні плат власної розробки код та проекти мають бути змінені, про це буде детальніше розказано в останньому розділі.

Для запуску прикладу необхідно виконати низку кроків:

  • запустити готовий проект із директорії з необхідним прикладом. Для простоти можна скористатися готовими проектами серед ARM Keil або EWARM, розташованими в папці MDK-ARM і EWARM відповідно;
  • включити живлення налагоджувальної плати STM32L053 Discovery/STM32F072 Discovery;
  • підключити живлення налагоджувальної плати до ПК за допомогою кабелю USB. Завдяки вбудованому налагоджувачу ST-Link/V2 додаткового програматора не потрібно;
  • відкрити, налаштувати та запустити проект;
    • Для ARM Keil:
      • відкрити проект;
      • скомпілювати проект – Project → Rebuild all target files;
      • завантажити його у контролер – Debug → Start/Stop Debug Session;
      • запустити програму у вікні Debug → Run (F5).
    • Для EWARM:
      • відкрити проект;
      • скомпілювати проект – Project → Rebuild all;
      • завантажити його в контролер - Project → Debug;
      • запустити програму у вікні Debug → Go(F5).
  • провести тестування відповідно до алгоритму, описаного в main.c.

Для аналізу програмного коду розглянемо конкретний приклад з STM32SnippetsL0: Projects \ LPUART \ 01_WakeUpFromLPM \.

Запуск прикладу для LPUART

Відмінною особливістю нових мікроконтролерів сімейства STM32L0 на ядрі Cortex-M0+ є можливість динамічної зміниспоживання за рахунок великої кількості нововведень. Одним з таких нововведень стала поява Low Power-периферії: 16-бітного таймера LPTIM та приймача LPUART. Ці блоки мають здатність тактування, що не залежить від тактування основної периферійної шини APB. При необхідності зниження споживаної потужності робоча частоташини APB (PCLK) може бути зменшена, а сам контролер переведено у режим зниженого споживання. При цьому Low Power-периферія продовжує роботу з максимальною продуктивністю.

Розглянемо приклад з директорії Projects \ LPUART \ 01_WakeUpFromLPM \, в якому розглядається можливість незалежної роботи LPUART в режимі зниженого споживання.

При відкритті проекту в середовищі ARM Keil відображаються лише три файли: startup_stm32l053xx.s, system_stm32l0xx.c та main.c (малюнок 4). У разі застосування стандартної бібліотеки до проекту потрібно було б додати файли драйверів.

Функціонування та аналіз структури файлу Main.c

Програма вибраного прикладу виконується в кілька етапів.

Після старту запускається функція SystemInit(), реалізована в system_stm32l0xx.c. Вона проводить налаштування параметрів блоку тактування RCC (таймінги та робочі частоти). Далі здійснюється передача управління основну функцію int main(void). У ній ініціалізується користувальницька периферія – порти вводу/виводу, LPUART – після чого контролер переводиться у режим зниженого споживання STOP. У ньому звичайна периферія та ядро ​​зупинено, працює тільки LPUART. Він чекає на початок передачі даних від зовнішнього пристрою. При приході стартового біта LPUART пробуджує систему та приймає повідомлення. Прийом супроводжується мерехтінням світлодіода налагоджувальної плати. Після цього контролер знову переводиться в стан STOP і чекає на наступну передачу даних, якщо не було виявлено помилок.

Передача даних відбувається за допомогою віртуального COM-порту та додаткового ПЗ.

Розглянемо main.c із нашого проекту. Цей файл є стандартний С-файл. Головною його особливістю є самодокументація – наявність докладних коментарів, пояснень та рекомендацій. Пояснювальна частина містить кілька розділів:

  • заголовок із зазначенням назви файлу, версії, дати, автора, короткого пояснення призначення;
  • опис послідовності налаштування системної периферії (RCC specific features): FLASH, ОЗУ, системи живлення та тактування, периферійних шин тощо;
  • перелік використовуваних ресурсів мікроконтролера (MCU Resources);
  • коротке пояснення щодо використання даного прикладу(How to use this example);
  • коротке пояснення щодо тестування прикладу та алгоритм його проведення (How to test this example).

Функція int main(void) має компактну форму і має коментарі, які в лістингу 1, для більшої наочності, перекладені російською.

Лістинг 1. Приклад реалізації функції main

int main(void)
{
/* До початку виконання цієї частини коли вже проведена конфігурація системних блоків функції SystemInit(), реалізованої в system_stm32l0xx.c. */
/* конфігурація периферійних блоків*/
Configure_GPIO_LED();
Configure_GPIO_LPUART();
Configure_LPUART();
Configure_LPM_Stop();
/* перевірка наявності помилок прийому */
while (! error) / * безкінечний цикл */
{
/* очікування готовності LPUART та перехід у режим STOP */
if((LPUART1->ISR & USART_ISR_REACK) == USART_ISR_REACK)
{
__WFI();
}
}
/* у разі виникнення помилки */
SysTick_Config (2000); /* встановлення періоду переривань системного таймера 1 мс */
while(1);
}

У файлі main.c оголошено та визначено функції конфігурації периферії та дві функції обробки переривань. Розглянемо їх особливості.

У наведеному прикладі використовуються чотири функції конфігурації (листинг 2). Усі вони не мають аргументів та не повертають значень. Їхнє головне призначення – швидко і з найменшими витратами займаного коду провести ініціалізацію периферії. Це реалізується за рахунок двох особливостей: застосування прямого звернення до регістрів та використання директиви __INLINE (листинг 3).

Лістинг 2. Оголошення функцій конфігурації периферії

void Configure_GPIO_LED(void);
void Configure_GPIO_LPUART(void);
void Configure_LPUART(void);
void Configure_LPM_Stop(void);

Лістинг 3. Приклад реалізації __INLINE-функції з прямим доступом до регістрів LPUART

INLINE void Configure_LPUART(void)
{
/* (1) Enable power interface clock */
/* (2) Заборонити захист реєстратора для того, щоб отримати доступ до RTC clock domain */
/* (3) LSE on */
/* (4) Wait LSE ready */
/* (5) Enable back up protection register to allow the access to the RTC clock domain */
/* (6) LSE mapped on LPUART */
/* (7) Enable peripheral clock LPUART */
/* Configure LPUART */
/* (8) oversampling by 16, 9600 baud */
/* (9) 8 data bit, 1 start bit, 1 stop bit, no parity, reception mode, stop mode */
/* (10) Set priority for LPUART1_IRQn */
/* (11) Enable LPUART1_IRQn */
RCC->APB1ENR | = (RCC_APB1ENR_PWREN); /* (1) */
PWR->CR | = PWR_CR_DBP; /* (2) */
RCC-> CSR | = RCC_CSR_LSEON; /* (3) */
while ((RCC-> CSR & (RCC_CSR_LSERDY)) != (RCC_CSR_LSERDY)) /*(4)*/
{
/* add time out here for a robust application */
}
PWR->CR &=~ PWR_CR_DBP; /* (5) */
RCC-> CCIPR | = RCC_CCIPR_LPUART1SEL; /* (6) */
RCC->APB1ENR | = RCC_APB1ENR_LPUART1EN; /*(7) */
LPUART1-> BRR = 0x369; /* (8) */
LPUART1->CR1 = USART_CR1_UESM | USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_UE; /* (9) */
NVIC_SetPriority(LPUART1_IRQn, 0); /* (10) */
NVIC_EnableIRQ(LPUART1_IRQn); /* (11) */
}

Обробники переривань від системного таймера та LPUART також використовують пряме звернення до регістрів.

Таким чином, спілкування з CMSIS здійснюється без стандартної бібліотеки. Код виявляється компактним та високоефективним. Однак його читальність значно погіршиться через велику кількість звернень до регістрів.

Використання сніпетів у власних розробках

Запропоновані набори сніпетів мають обмеження: необхідно використовувати налагоджувальну плату STM32L053 Discovery board для STM32SnippetsL0, а плату STM32F072 Discovery board - для STM32SnippetsF0.

Для застосування сніпетів у своїх розробках потрібно зробити низку змін. По-перше, необхідно переконфігурувати проект під потрібний процесор. Для цього в ньому потрібно змінити стартовий файл startup_stm32l053xx.s на файл іншого контролера і визначити потрібну константу: STM32L051xx, STM32L052xx, STM32L053xx, STM32L062xx, STM32L063x2 031, STM32F051 та інші. Після цього при компіляції stm32l0xx.h буде автоматично підключений потрібний файл з визначенням периферії контролера stm32l0yyxx.h (stm32l051xx.h/stm32l052xx.h/stm32l053xx.h/stm32l0. 63). По-друге, потрібно вибрати відповідний програматор у налаштуваннях властивостей проекту. По-третє – змінити код функцій з прикладів, якщо вони не відповідають вимогам програми користувача.

Висновок

Набори сніпетів та стандартні бібліотеки периферії виробництва компанії ST Microelectronics не є взаємовиключними. Вони доповнюють один одного, додаючи гнучкість під час створення додатків.

Стандартна бібліотека дає можливість швидкого створення чіткого коду з високим рівнем абстракції.

Сніпети дозволяють підвищити ефективність коду - збільшити продуктивність і скоротити обсяг займаної пам'яті FLASHта ОЗУ.

Література

  1. Data brief. STM32SnippetsF0. STM32F0xx Snippets firmware package. Rev. 1. - ST Microelectronics, 2014.
  2. Data brief. STM32SnippetsL0. STM32F0xx Snippets firmware package. Rev. 1. - ST Microelectronics, 2014.
  3. MCD-ST Liberty SW License Agreement V2.pdfElectromechanickі Relays. Технологічна інформація. - ST Microelectronics, 2011.
  4. Data brief. 32L0538DISCOVERY Discovery kit для STM32L053 microcontrollers. Rev. 1. - ST Microelectronics, 2014.
  5. http://www.st.com/.
Про компанію ST Microelectronics

Отже, на ноги ми вже встали, у сенсі на висновки мікроконтролера на платі STM32VL Discovery у нас підключено все що треба, говорити ми навчилися, мовою програмування Сі, настав час і в перший клас проект створити.

Написання програми

Закінчивши зі створенням та налаштуванням проекту, можна приступити до написання реальної програми. Як повелося у всіх програмістів, першою програмою написаної для роботи на комп'ютері, є програма, що виводить на екран напис «HelloWorld», так і у всіх мікроконтролерів перша програма для мікроконтролера робить блимання світлодіода. Ми не будемо винятком із цієї традиції та напишемо програму, яка керуватиме світлодіодом LD3 на платі STM32VL Discovery.

Після створення порожнього проекту в IAR, він створює мінімальний код програми:

Тепер наша програма завжди «крутитиметься» у циклі while.

Для того, щоб ми могли керувати світлодіодом, нам необхідно дозволити тактування порту, до якого він підключений, і налаштувати відповідний висновок порту мікроконтролера на вихід. Як ми вже розглядали раніше у першій частині, за дозвіл тактування порту Звідповідає біт IOPCENрегістра RCC_APB2ENR. Згідно з документом « RM0041Referencemanual.pdf» для дозволу тактування шини порту Знеобхідно в регістрі RCC_APB2ENRвстановити біт IOPCENза одиницю. Щоб при встановленні даного біта, ми не скинули інші, встановлені в даному регістрі, нам необхідно до поточного стану регістру застосувати операцію логічного складання (логічного «АБО») і після цього записати отримане значення вміст регістру. Відповідно до структури бібліотеки ST, звернення до значення регістру для його читання та запису здійснюється через покажчик на структуру RCC-> APB2 ENR. Таким чином, згадуючи матеріал з другої частини, можна записати наступний код, що виконує встановлення біта IOPCENу регістрі RCC_APB2ENR:

Як можна переконатися, з файлу "stm32f10x.h", значення біта IOPCENвизначено як 0x00000010, що відповідає четвертому біту ( IOPCEN) регістра APB2ENRі збігається зі значенням, зазначеним у датасіті.

Тепер аналогічним чином налаштуємо висновок 9 порту З. Для цього нам необхідно налаштувати виведення порту на вихід у режимі push-pull. За налаштування режиму порту на вхід/вихід відповідає регістр GPIOC_CRH, ми його вже розглядали в , його опис також знаходиться в розділі "7.2.2 Port configuration register high" даташита. Для налаштування виведення в режим виходу з максимальною швидкодією 2МГц, необхідно у регістрі GPIOC_CRHвстановити MODE9в одиницю та скинути біт MODE9на нуль. За налаштування режиму роботи виведення як основна функція з виходом push-pull відповідають біти CNF9 і CNF9 Для налаштування необхідного режиму роботи, обидва ці біти повинні бути скинуті в нуль.

Тепер виведення порту, до якого підключено світлодіод, налаштований на вихід, для керування світлодіодом нам необхідно змінити стан виведення порту, встановивши на виході логічну одиницю. Для зміни стану виведення порту існує два способи, перший запис безпосередньо в регістр стану порту зміненого вмісту регістру порту, так само як ми проводили налаштування порту. Даний спосіб не рекомендується використовувати через можливість виникнення ситуації, при якій в регістр порту може записатися не правильне значення. Ця ситуаціяможе виникнути якщо під час зміни стану регістру, з часу коли вже було здійснено читання стану регістру і до моменту коли відбудеться запис зміненого стану в регістр, яке або периферійний пристрійабо переривання змінить стан цього порту. Після завершення операції зі зміни стану регістру відбудеться запис значення в регістр без урахування змін, що відбулися. Хоча ймовірність виникнення цієї ситуації є дуже низькою, все ж таки варто користуватися іншим способом, при якому описана ситуація виключена. Для цього в мікроконтролері існує два регістри GPIOx_BSRRі GPIOx_BRR. При записі логічної одиниці в потрібний біт регістра GPIOx_BRRвідбудеться скидання відповідного виведення порту у логічний нуль. Реєстр GPIOx_BSRRможе проводити як установку, так і скидання стану висновків порту, для встановлення виведення порту в логічну одиницю необхідно провести установку бітів BSn, відповідних номеру необхідного біта, дані біти розміщуються у молодших регістрах байта. Для скидання стану виведення порту в логічний нуль, необхідно зробити запис бітів BRnвідповідних висновків, ці біти розташовуються у старших розрядах регістру порту.

Світлодіод LD3 підключений до виводу 9 порту З. Для включення даного світлодіода, нам необхідно подати на відповідному виведенні порту логічну одиницю, щоб запалити світлодіод.

Додамо код налаштування виведення порту світлодіода в нашу програму, а також додамо функцію програмної затримки для зменшення частоти перемикання світлодіода:

//Не забуваємо підключити заголовний файл з описом регістрів мікроконтролера

#include "stm32f10x.h"

void Delay ( void);

void Delay ( void)
{
unsigned long i;
for(i=0; i<2000000; i++);
}

//Наша головна функція

void main( void)
{


RCC->APB2ENR | = RCC_APB2ENR_IOPCEN;

//очистимо розряди MODE9 (скинути біти MODE9_1 та MODE9_0 в нуль)
GPIOC->CRH &= ~GPIO_CRH_MODE9;

//Виставимо біт MODE9_1, для налаштування виведення на вихід із швидкодією 2MHz
GPIOC->CRH | = GPIO_CRH_MODE9_1;

//очистимо розряди CNF (налаштувати як вихід загального призначення, симетричний (push-pull))
GPIOC->CRH &= ~GPIO_CRH_CNF9;

while(1)
{

//Установка виведення 9 порту С у логічну одиницю («запалили» світлодіод)
GPIOC->BSRR = GPIO_BSRR_BS9;


Delay ();


GPIOC->BSRR = GPIO_BSRR_BR9;


Delay ();

}
}

Завантажити архів з вихідним кодом програми, написаної з використанням безпосереднього керування регістрами мікроконтролера можна за посиланням.

Наша перша працездатна програма написана, при її написанні, для роботи та налаштування периферії, ми користувалися даними з офіційного даташита RM0041Referencemanual.pdf», дане джерелоінформації про регістри мікроконтролера є найточнішим, але для того, щоб ним користуватися, доводиться перечитувати багато інформації, що ускладнює написання програм. Для полегшення процесу налаштування периферії мікроконтролера, існують різні генератори коду, офіційною утилітою від компанії ST представлена ​​програма Microxplorer, але вона поки що малофункціональна і тому сторонніми розробниками була створена альтернативна програма «STM32 Генератор програмного коду » . Дана програма дозволяє легко отримати код налаштування периферії, використовуючи зручний, наочний графічний інтерфейс(Див. рис. 2).


Мал. 2 Скріншот програми STM32 генератор коду

Як видно з малюнку 2, згенерований програмою код налаштування виведення світлодіода збігається з кодом, написаним нами раніше.

Для запуску написаної програми після виконання компіляції вихідного кодунеобхідно завантажити нашу програму в мікроконтролер і подивитися, як вона виконується.

Відео режиму налагодження програми миготіння світлодіодом

Відео роботи програми миготіння світлодіодом на платі STM32VL Discovery

Бібліотечні функції роботи з периферією

Для спрощення роботи з налаштуванням регістрів периферії мікроконтролера компанія ST розробила бібліотеки, завдяки використанню яких не потрібно так досконало читати даташит, оскільки при використанні даних бібліотек робота по написанню програми стане більш наближеною до написання програм. високого рівня, через те, що це низькорівневі функції реалізуються лише на рівні функцій бібліотеки. Однак не слід повністю відмовлятися від використання безпосередньої роботи з регістрами мікроконтролера, тому що бібліотечні функції вимагають більше процесорного часу на своє виконання, як наслідок їх використання в критичних за часом виконання ділянках програми не виправдано. Але все ж таки, в більшості випадків, такі речі як ініціалізація периферії, не критичні на час виконання, і зручність використання бібліотечних функцій виявляється кращим.

Тепер напишемо нашу програму за допомогою бібліотеки ST. У програмі потрібно зробити налаштування портів вводу/виводу, для використання бібліотечних функцій налаштування портів, необхідно з'єднати заголовний файл « stm32f10x_gpio.h»(див. табл. 1). Підключення даного файлу можна зробити розкоментуванням відповідного рядка в підключеному заголовному конфігураційному файлі « stm32f10x_conf.h». Наприкінці файлу « stm32f10x_gpio.h» є список об'яв функцій для роботи з портами. Докладний описвсіх наявних функцій можна прочитати у файлі « stm32f10x_stdperiph_lib_um.chm», короткий описнайчастіше застосовуваних наведено у таблиці 2.

Таблиця 2.Опис основних функцій налаштування портів

Функція

Опис функції, що передаються та повертаються параметрів

GPIO_DeInit (
GPIO_TypeDef* GPIOx)

Здійснює встановлення значень регістрів налаштування порту GPIOx на значення за замовчуванням

GPIO_Init (
GPIO_TypeDef* GPIOx,

Здійснює встановлення регістрів налаштування порту GPIOx відповідно до зазначених параметрів у структурі GPIO_InitStruct

GPIO_StructInit (
GPIO_InitTypeDef* GPIO_InitStruct)

Заповнює всі поля структури GPIO_InitStruct, значеннями за замовчуванням

uint8_t GPIO_ReadInputDataBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin);

Читання вхідного значення виведення GPIO_Pin порту GPIOx

uint16_t GPIO_ReadInputData (
GPIO_TypeDef* GPIOx)

Читання вхідних значень всіх висновків порту GPIOx

GPIO_SetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Встановлення вихідного значення виведення GPIO_Pin порту GPIOx у логічну одиницю

GPIO_ResetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Скидання вихідного значення виведення GPIO_Pin порту GPIOx у логічний нуль

GPIO_WriteBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin,
BitAction BitVal)

Запис BitVal у виведення GPIO_Pin порту GPIOx

GPIO_Write(
GPIO_TypeDef* GPIOx,
uint16_t PortVal)

Запис значення PortVal до порту GPIOx

Як видно з опису функцій, як параметри налаштувань порту і т.п., в функцію передають не безліч різних окремих параметрів, а одну структуру. Структури - це об'єднані дані, які мають певний логічний взаємозв'язок. На відміну від масивів структури можуть містити дані різних типів. Іншими словами, структура представляє набір різних змінних з різними типамиоб'єднаними в одну своєрідну змінну. Змінні, що у цій структурі називаються полями структури, а звернення до них проводиться так, спершу пишеться ім'я структури, потім пишеться точка і ім'я поля структури (ім'я змінної у цій структурі).

Список змінних, включених у структури для функцій роботи з портами, описані у тому файлі трохи вище описи функцій. Так, наприклад, структура « GPIO_InitTypeDef» має наступну структуру:

typedef struct
{

uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
Цей параметр може бути будь-яким значенням @ref GPIO_pins_define */

GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
Цей параметр може бути значенням @ref GPIOSpeed_TypeDef */

GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
Цей параметр може бути значенням @ref GPIOMode_TypeDef */

) GPIO_InitTypeDef;

Перше поле даної структури містить змінну GPIO_ Pin» типу unsigned short, у цю змінну необхідно записувати прапори номерів відповідних висновків, котрим передбачається зробити необхідну настройку. Можна налаштувати відразу кілька висновків, задавши як параметр кілька констант через оператор побітове АБО(Див. ). Побітове АБО «збере» всі одиниці з перерахованих констант, а самі константи є маскою, призначеної для такого використання. Макровизначення констант вказані в цьому файлі нижче.

Друге поле структури GPIO_InitTypeDef» Вказує максимально можливу швидкість роботи виходу порту. Список можливих значень даного поля перераховано вище:

Опис можливих значень:

  • GPIO_Mode_AIN- аналоговий вхід (англ. Analog INput);
  • GPIO_Mode_IN_FLOATING- вхід без підтяжки, що бовтається (англ. Input float) у повітрі
  • GPIO_Mode_IPD- Вхід з підтяжкою до землі (Input Pull-down)
  • GPIO_Mode_IPU- Вхід з підтяжкою до харчування (Input Pull-up)
  • GPIO_Mode_Out_OD- Вихід з відкритим стоком (англ. Output Open Drain)
  • GPIO_Mode_Out_PP- Вихід двома станами (англ. Output Push-Pull - туди-сюди)
  • GPIO_Mode_AF_OD- Вихід з відкритим стоком для альтернативних функцій (Alternate Function). Використовується у випадках, коли висновком має керувати периферія, прикріплена до даному висновкупорту (наприклад, виведення Tx USART1 тощо)
  • GPIO_Mode_AF_PP- те саме, але з двома станами

Аналогічним чином можна побачити структуру змінних інших структур, необхідні роботи з бібліотечними функціями.

Для роботи зі структурами, їх так само як і змінні, необхідно оголосити та привласнити їм унікальне ім'я, після чого можна звертатися до полів оголошеної структури за присвоєним їй ім'ям.

//Оголошуємо структуру

/*
Перш ніж почати заповнення полів структури, рекомендується проініціалізувати вміст структури даними за умовчанням, це робиться з метою запобігання запису невірних даних, якщо з якоїсь причини не всі поля структури були заповнені.

Для передачі значень структури до функції необхідно перед ім'ям структури поставити символ &. Цей символ говорить компілятору, що необхідно передавати функції не самі значення, що містяться в структурі, а адресу в пам'яті, за якою розміщуються дані значення. Це робиться для того, щоб зменшити кількість необхідних дійпроцесора з копіювання вмісту структури, а також дозволяє заощаджувати оперативну пам'ять. Таким чином, замість передачі в функцію безлічі байт, що містяться в структурі, буде переданий тільки один, що містить адресу структури.
*/

/* Запишемо в полі GPIO_Pin структури GPIO_Init_struct номер виведення порту, який ми налаштовуватимемо далі */

GPIO_Init_struct.GPIO_Pin=GPIO_Pin_9;

/* Подібним чином заповнимо поле GPIO_Speed ​​*/

/*
Після того, як ми заповнили необхідні поляструктури, дану структуру необхідно передати у функцію, яка здійснить необхідний запис у відповідні регістри. Крім структури з налаштуваннями цієї функції також необхідно передати ім'я порту, для якого призначені налаштування.
*/

Практично вся периферія налаштовується приблизно так само, відмінності є лише у специфічних кожному за пристрою параметрах і командах.

Тепер напишемо нашу програму миготіння світлодіодом із використанням лише бібліотечних функцій.

//Не забуваємо підключити заголовний файл з описом регістрів мікроконтролера

#include "stm32f10x.h"
#include "stm32f10x_conf.h"

//оголошуємо функцію програмної затримки

void Delay ( void);

//сама функція програмної затримки

void Delay ( void)
{
unsigned long i;
for(i=0; i<2000000; i++);
}

//Наша головна функція

void main( void)
{

// Дозволяємо тактування шини порту С
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Оголошуємо структуру для налаштування порту
GPIO_InitTypeDef GPIO_Init_struct;

//Заповнюємо структуру початковими значеннями
GPIO_StructInit(&GPIO_Init_struct);

/* Запишемо в полі GPIO_Pin структури GPIO_Init_struct номер виведення порту, який ми налаштовуватимемо далі */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Подібним чином заповнимо поля GPIO_Speed ​​та GPIO_Mode
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

//Передаємо заповнену структуру, для виконання дій з налаштування регістрів
GPIO_Init(GPIOC, &GPIO_Init_struct);

//Наш основний нескінченний цикл
while(1)
{
//Установка виведення 9 порту С у логічну одиницю ("запалили" світлодіод)
GPIO_SetBits(GPIOC, GPIO_Pin_9);

//Додаємо програмну затримку, щоб світлодіод світився деякий час
Delay ();

//Скидання стану виведення 9 порту С у логічний нуль
GPIO_ResetBits(GPIOC, GPIO_Pin_9);

//Додаємо знову програмну затримку
Delay ();
}
}

посилання.

З наведеного вище прикладу видно, що використання бібліотечних функцій роботи з периферією дозволяє наблизити написання програм для мікроконтролера до об'єктно-орієнтованого програмування, а також знижує необхідність у частому зверненні до даташиту для читання опису регістрів мікроконтролера, але використання бібліотечних функцій потребує більш високих знань мови програмування . Зважаючи на це, для людей не дуже близько знайомих з програмуванням, більш простим варіантом написання програм буде спосіб написання програм без використання бібліотечних функцій, з прямим зверненням до регістрів мікроконтролера. Для тих, хто добре знає мову програмування, але погано розуміється на мікроконтролерах, зокрема STM32, використання бібліотечних функцій істотно спрощує процес написання програм.

Ця обставина, а також той факт, що компанія ST подбала про високий рівень сумісності, як в апаратному, так і в програмному плані, різних своїх мікроконтролерів, сприяє більш простому їх вивченню, тому що не потрібно заглиблюватися на особливості будови різних контролерів серії STM32 дозволяє в якості мікроконтролера для вивчення вибрати будь-який з наявних в лінійці STM32 мікроконтролер.

Обробник переривання

Мікроконтролери мають одну чудову здатність – зупиняти виконання основної програми за якоюсь певною подією, та переходити до виконання спеціальної підпрограми – обробнику переривання. Як джерела переривання можуть виступати як зовнішні події – переривання по прийому/передачі даних через якийсь інтерфейс передачі даних, або зміна стану виведення, так і внутрішні – переповнення таймера тощо. Список можливих джерел переривання для мікроконтролерів серії STM32 наведено в даташ « RM0041 Reference manual" в розділі " 8 Interrupts and events».

Оскільки обробник переривання також є функцією, то і записуватися вона буде як звичайна функція, але щоб компілятор знав, що дана функція є обробником певного переривання, як ім'я функції слід вибрати заздалегідь визначені імена, на які вказані перенаправлення векторів переривання. Список імен цих функцій з коротким описом міститься в асемблерному файлі « startup_stm32f10x_md_vl.s». На один обробник переривання може припадати кілька джерел, що викликають переривання, наприклад функція обробник переривання. USART1_IRQHandler» може бути викликана у разі закінчення прийому та закінчення передачі байта і т.д.

Для початку роботи з перериваннями слід налаштувати та проініціалізувати контролер переривань NVIC. В архітектурі Cortex M3 кожному перериванню можна виставити свою групу пріоритету для випадків коли виникає кілька переривань одночасно. Потім слід налаштувати джерело переривання.

У полі NVIC_IRQChannel вказується, яке саме переривання хочемо налаштувати. Константа USART1_IRQn означає канал, який відповідає за переривання, пов'язані з USART1. Вона визначена у файлі « stm32f10x.h», там само визначено інші подібні константи.

У двох полях вказується пріоритет переривань (максимальні значення цих двох параметрів визначаються обраною пріоритетною групою). Останнє поле, власне, включає використання переривання.

У функцію NVIC_Init, так само як і при настроюванні портів передається покажчик на структуру для застосування внесених налаштувань та запису їх у відповідні регістри мікроконтролера.

Тепер у налаштуваннях модуля необхідно встановити параметри, за якими цей модуль генеруватиме переривання. Для початку слід зробити включення переривання, це робиться викликом функції name_ITConfig(), яка знаходиться заголовному файлі периферійного пристрою.

// Дозволяємо переривання після закінчення передачі байта по USART1
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);

// Дозволяємо переривання після закінчення прийому байта по USART1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

Опис параметрів, що передаються функції, можна подивитися у файлі вихідного коду периферійного пристрою, трохи вище за розташування самої функції. Ця функція дозволяє або забороняє переривання з різних подій від зазначеного периферійного модуля. Коли ця функція буде виконана, мікроконтролер зможе генерувати переривання на необхідні події.

Після того як ми потрапимо у функцію обробки переривання, нам необхідно перевірити від якої події відбулося переривання, а потім скинути зведений прапор, інакше після виходу з переривання мікроконтролер вирішить, що ми не обробили переривання, оскільки прапор переривання все ще встановлений.

Для виконання різних, невеликих, що повторюються з точним періодом дій, мікроконтролери з ядром Cortex-M3 є спеціально призначений для цього системний таймер. У функції даного таймера входить лише виклик переривання через задані інтервали часу. Як правило, у перериванні, що викликається цим таймером, розміщують код для вимірювання тривалості різних процесів. Оголошення функції налаштування таймера розміщено у файлі « core_ cm3. h». Як передається функцію аргументу вказується число тактів системної шини між інтервалами виклику обробника переривання системного таймера.

SysTick_Config(clk);

Тепер розібравшись з перериваннями, перепишемо нашу програму, використовуючи як час, що задає елемента, системний таймер. Оскільки таймер « SysTick» є системним і ним можуть користуватись різні функціональні блоки нашої програми, то розумним буде винести функцію обробки переривання від системного таймера в окремий файл, з цієї функції викликати функції для кожного функціонального блоку окремо.

Приклад файлу "main.с" програми миготіння світлодіода з використанням переривання:

//Підключаємо заголовний файл з описом регістрів мікроконтролера

#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "main.h"

unsigned int LED_timer;

//Функція, що викликається з функції-обробника переривань системного таймера

void SysTick_Timer_main( void)
{
//Якщо змінна LED_timer ще не дійшла до 0,
if(LED_timer)
{
//Перевіряємо її значення, якщо воно більше 1500 включимо світлодіод
if(LED_timer>1500) GPIOC->BSRR = GPIO_BSRR_BS9;

//інакше якщо менше або дорівнює 1500, то виключимо
else GPIOC->BSRR = GPIO_BSRR_BR9;

//Зробимо декремент змінної LED_timer
LED_timer--;
}

//Якщо значення змінної дійшло до нуля, задамо нове значення 2000
else LED_timer=2000;
}

//Наша головна функція

void main( void)
{

// Дозволяємо тактування шини порту С
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Оголошуємо структуру для налаштування порту
GPIO_InitTypeDef GPIO_Init_struct;

//Заповнюємо структуру початковими значеннями
GPIO_StructInit(&GPIO_Init_struct);

/* Запишемо в полі GPIO_Pin структури GPIO_Init_struct номер виведення порту, який ми налаштовуватимемо далі */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Подібним чином заповнимо поля GPIO_Speed ​​та GPIO_Mode
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

//Передаємо заповнену структуру, для виконання дій з налаштування регістрів
GPIO_Init(GPIOC, &GPIO_Init_struct);

// Вибираємо пріоритетну групу для переривань
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

//Налаштовуємо роботу системного таймера з інтервалом 1мс
SysTick_Config (24000000/1000);

//Наш основний нескінченний цикл
while(1)
{
//Цього разу тут порожньо, все керування світлодіодом відбувається у перериваннях
}
}

Частина вихідного коду у файлі "stm32f10x_it.c":


#include "main.h"

/**
* @brief Цей function handles SysTick Handler.
* @param None
* @retval None
*/

void SysTick_Handler( void)
{
SysTick_Timer_main();
}

Приклад робочого проекту програми миготіння світлодіода з використанням переривання можна завантажити за посиланням.

На цьому моя розповідь про основи розробки програм для мікроконтролера STM32 можна вважати завершеною. Я надав всю інформацію, необхідну для подальшого самостійного вивченнямікроконтролерів STM32. Наданий матеріал є лише стартовим, оскільки повний опис роботи з мікроконтролерами неможливо описати в рамках будь-якої статті. Крім цього, вивчення мікроконтролерів без отримання практичного досвіду неможливе, а цей досвід приходить поступово з роками роботи, експериментами, з накопиченням різних програмних та апаратних напрацювань, а також читанні різних статей та документації з мікроконтролерів. Але нехай це Вас не лякає, оскільки наданої в статті інформації цілком достатньо для створення свого першого пристрою на мікроконтролері, а подальші знання та досвід Ви зможете придбати самостійно, розробляючи з кожним разом все більш складні та кращі пристрої та вдосконалюючи свою майстерність.

Сподіваюся, я зміг зацікавити Вас зайнятися вивченням мікроконтролерів та розробкою пристроїв на них, і мої праці будуть Вам корисні та цікаві.

Ще раз хочу написати про простий старт з STM32, тільки цього разу без використання чиїхось шаблонів або прикладів - з поясненням кожного кроку. У статтях буде наскрізна нумерація кроків.

1. Встановлюємо IAR

Складання проекту в IAR

1. Препроцесор

  1. видаляє коментарі

2. Компілятор

3. Лінкер

3. Створюємо новий проект у IAR

Після запуску IAR з'являється вікно інформаційного центру, який нам не потрібний. Натискаємо меню Project -> Create New Project. Вибираємо toolchain: ARM (навряд чи у вас буде щось ще в тому списку), Project templates: C –> main.

У лівому вікні («Workspace») правою кнопкою миші викликаємо меню та створюємо нову групу(Add ->

Клацаємо правою кнопкою по CMSIS

До групи Startup

Із CMSIS закінчили.

До групи StdPeriphLib

До групи User

5. Налаштовуємо проект

  1. General options –> Target –>
Вибираємо ST -> STM32F100 -> ST STM32F100xB. Це наш контролер. 2. General options -> Library Configuration -> CMSIS: ставимо галочку Use CMSIS. Так ми будемо використовувати вбудовану в компілятор бібліотеку CMSIS. З версії 6.30 IAR став поставлятися з вбудованою CMSIS, і це начебто краще - але внесло деяку плутанину зі старими проектами. 3. C/C++ compiler ->
$PROJ_DIR$\

* Debugger -> Setup -> Driver: вибираємо ST-Link, оскільки саме такий програматор вбудований у плату Discovery. Тепер налаштовуємо сам програматор: * Debugger -> ST-LINK -> Interface: вибираємо SWD (програматор на платі підключений до контролера SWD, а не JTAG). * Debugger ->
#include "stm32f10x_conf.h" 

void main()
{
while(1)
{
}
}

<1000000; i++);


for(i=0; i<1000000; i++);

#include "stm32f10x_conf.h"

void main()
{





int i;
while(1)
{

for(i=0; i<1000000; i++);

<1000000; i++); } }

архів із проектом GPIO. На щастя, цей проект можна зберегти і далі використовувати його як шаблон, щоб не проводити все налаштування заново. Весь цикл: 1. Порти введення-виводу (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - з нуля до RTOS. 2: Таймер і переривання") (/index.php/stm32-from_zero_to_rtos-3_timer_out - з нуля до RTOS. 3: Виходи таймера") [Ще раз хочу написати про простий старт з STM32, тільки цього разу без використання чиїхось шаблонів або прикладів - з поясненням кожного кроку. У статтях буде наскрізна нумерація кроків.

0. Добуваємо плату STM32VLDiscovery

Купуємо у магазині, коштує 600 рублів. Потрібно буде встановити драйвера на плату - я думаю, це труднощів не викличе.

1. Встановлюємо IAR

Працюватимемо в IAR – гарна IDE з чудовим компілятором. Їй не вистачає зручності написання коду - але для наших цілей її цілком достатньо. Я використовую IAR версії 6.50.3 взяти - самі знаєте де.

2. Завантажуємо бібліотеку периферії

Я не прихильник роботи з регістрами на етапі навчання. Тому пропоную завантажити бібліотеку периферії від ST для отримання зручних функцій доступу до всіх налаштувань.

Створюємо папку «STM32_Projects», складаємо туди папку Libraries зі скачаного архіву (stsw-stm32078.zip/an3268/stm32vldiscovery_package), в ній лежить CMSIS (бібліотека від ARM для всіх мікроконтролерів Cortx2 - бібліотека периферії від ST з усіма функціями.

Також утворюємо там папку «1. GPIO», в якій і лежатиме наш перший проект.

Дерево папок - малюнок. Зробіть саме так, тому що потім будуть дуже важливими відносні шляхи в цьому дереві.

Ну і щоб розуміти, про що йдеться - скачайте 1100-сторінковий документ на ці контролери.

Складання проекту в IAR

Необхідно чітко розуміти суть процесу складання проекту. Для зручності розіб'ємо її на стадії.

1. Препроцесор

По всіх файлах проекту (і по main.c, і по всіх файлах у workspace) проходить препроцесор. Він робить таке:

  1. видаляє коментарі
  2. розкриває директиви #include, підставляючи замість них вміст зазначеного файлу. Цей процес проходить рекурсивно, починаючи з c-файлу і заходячи в кожен зустрінутий #include .h, і якщо в.h-файлі теж зустрілися директиви #include - препроцесор зайде і в них. Виходить таке дерево інклудів. Зверніть увагу: не обробляє ситуацію подвійного включення інклудів, тобто. один і той же.h-файл може увімкнутися кілька разів, якщо він вказаний в #include в декількох місцях проекту. Таку ситуацію слід обробляти дефайнами.
  3. здійснює макропідстановки - розкриває макроси
  4. збирає директиви компілятора.

Препроцесор генерує файли.i, які досить зручні при пошуку помилок складання - хоча б тому, що в них повністю розкрито всі макроси. Збереження цих файлів можна увімкнути у налаштуваннях проекту.

На цьому етапі збирач має усі.c-файли в проекті, готові для компіляції - у вигляді файлів.i. Жодних зв'язків між файлами поки немає.

2. Компілятор

Після проходу препроцесора компілятор оптимізує та компілює кожен .i-файл, створюючи бінарний код. Саме тут потрібна вказівка ​​типу процесора, доступної пам'яті, мови програмування, рівня оптимізації та подібних речей.

Що робить компілятор, зустрічаючи в якомусь.c-файлі виклик функції, яка не описана в цьому файлі? Він шукає її у заголовках. Якщо заголовки говорять, що функція лежить в другому файлі - він просто залишає на цьому місці покажчик на цей інший файл.

На цьому етапі збирач має все.c-файли проекту, зібрані в.o-файли. Вони називаються скомпільованими модулями. Тепер між файлами є зв'язки у вигляді покажчиків у місцях виклику «чужих» функцій – але це досі кілька різних файлів.

3. Лінкер

Вже майже все готово, потрібно лише перевірити всі зв'язки між файлами – пройти по main.o та підставити на місце вказівників на чужі функції – скомпіловані модулі. Якщо якась функція з бібліотек не використовується - вона або взагалі не буде скомпілювана на попередньому етапі, або не буде нікуди підставлена ​​лінкером (залежить від методу роботи збирача). У будь-якому випадку до готового бінарного коду вона не потрапить.

Лінкер також може виконати якісь фінальні дії з бінарником, наприклад порахувати його контрольну суму.

Перший проект - робота з портами введення-виводу

3. Створюємо новий проект у IAR

Після запуску IAR з'являється вікно інформаційного центру, який нам не потрібний. Натискаємо меню Project -> Create New Project. Вибираємо toolchain: ARM (навряд чи у вас буде щось ще в тому списку), Project templates: C –> main.

Тепер у вас новий порожній проект на C і файл main.c.

4. Підключаємо до проекту бібліотеки

У лівому вікні («Workspace») правою кнопкою миші викликаємо меню та створюємо нову групу (Add –> Add Group), назвемо її CMSIS. Так само створимо групи StdPeriphLib, Startup і User. Тепер додаємо файли до груп (я підкреслю всі файли, щоб було зручніше стежити).

Клацаємо правою кнопкою по CMSIS, Add, Add files - заходимо в Libraries/CMSIS/CM3, з папки DeviceSupport/ST/STM32F10x (підтримка кристала) беремо system_stm32f10x.c (це опис периферії конкретного кристала та налаштування тактування). У папці CoreSupport (підтримка ядра) лежить ще й core_cm3.c (це опис ядра Cortex M3), але ми не станемо його брати – тому що він вже є у компіляторі. Я напишу про це далі.

До групи Startupскладаємо файл startup_stm32f10x_md_vl.s з папки Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/iar. Це – дії, які потрібно виконати при старті. Майже повністю це налаштування обробників різних переривань (самі обробники будуть трохи далі). Там ще є файли для інших кристалів, але нас цікавить саме md_vl - це означає medium density (середній обсяг пам'яті, є ще кристали з маленьким і великим об'ємом), value line (оцінна лінія - кристал STM32F100 призначений лише для оцінки можливостей та переходу на наступні сімейства).

Із CMSIS закінчили.

До групи StdPeriphLibдодаємо з папки Libraries/STM32F10x_StdPeriph_Driver/src файли stm32f10x_rcc.c та stm32f10x_gpio.c . Перший - це функції роботи з системою тактування, а другий - робота з ніжками введення-виведення.

До групи Userперетягуємо наш main.c. Це необов'язково, але так красивіше.

Тепер дерево проекту GPIO виглядає так:

Воркспейс готовий, більше додавати до нього нічого не будемо.

Залишилося тільки покласти в папку з проектом ще один файл, що включає заголовки до всіх файлів бібліотеки периферії. Можна його написати самостійно, але простіше взяти готовий. Заходимо до stsw-stm32078.zip/an3268/stm32vldiscovery_package/Project/Examples/GPIOToggle - там забираємо файл stm32f10x_conf.h (конфігурація проекту) і кладемо його в папку «1. GPIO». Це єдиний готовий файл, який ми беремо.

stm32f10x_conf.h - просто звалище інклудів потрібних модулів та функцій assert. Ця функція буде викликатися при помилках роботи з функціями бібліотеки периферії: наприклад, засунути в функцію GPIO_WriteBit замість GPIOC якусь фігню - коротше, ST чудово перестрахувалася. У цій функції можна просто запустити нескінченний цикл – while(1); Нам все одно потрібно лізти в stm32f10x_conf.h – щоб закоментувати рядки включення файлів непотрібної периферії, залишивши тільки stm32f10x_rcc.h, stm32f10x_gpio.h та misc.h – тому можна було написати його і самостійно.

5. Налаштовуємо проект

Натискаємо правою кнопкою миші у вікні Workspace за назвою проекту:

  1. General options -> Target -> Processor variant: вибираємо "Device", натискаємо кнопку правіше
Вибираємо ST -> STM32F100 -> ST STM32F100xB. Це наш контролер. 2. General options -> Library Configuration -> CMSIS: ставимо галочку Use CMSIS. Так ми будемо використовувати вбудовану в компілятор бібліотеку CMSIS. З версії 6.30 IAR став поставлятися з вбудованою CMSIS, і це начебто краще - але внесло деяку плутанину зі старими проектами. 3. C/C++ compiler -> Preprocessor. Тут пишемо шляхи до папок бібліотеки:
$PROJ_DIR$\
$PROJ_DIR$\..\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
$PROJ_DIR$\..\Libraries\STM32F10x_StdPeriph_Driver\inc
Макрос $PROJ_DIR$ означає поточну папку (папку проекту), а... - перехід на один рівень вище. Ми прописали шляхи до папки з описом кристала, а також до заголовних файлів бібліотеки периферії, оскільки все файли в проекті підключають свої заголовки, і компілятор повинен знати де їх шукати. Ще тут потрібно написати USE\_STDPERIPH\_DRIVER у Defined symbols. Це підключить потрібні файликонфігурації (наприклад, згаданий stm32f10x_conf.h) до проекту. Таким чином, вкладка Preprocessor буде виглядати так: * Debugger -> Setup -> Driver: вибираємо ST-Link, оскільки саме такий програматор вбудований у плату Discovery. Тепер налаштовуємо сам програматор: * Debugger -> ST-LINK -> Interface: вибираємо SWD (програматор на платі підключений до контролера SWD, а не JTAG). * Debugger –> Download: ставимо галочку Use flash loader(s), «Заливати прошивку у flash-пам'ять». Логічно, без неї нічого не заллється.## 6. Пишемо код А для початку напишу, що цей код робитиме. Він демонструватиме просту річ, блимання світлодіодом (PC8 на платі Discovery) з паузою в безкінечному циклі. Підключаємо заголовний файл конфігурації проекту, stm32f10x\_conf.h. У ньому знаходимо рядок #include "stm32f10x\_exti.h" - це 35 рядок, і закоментуємо її двома слішами. Справа в тому, що у нашому проекті не знадобиться модуль EXTI. У файлі main.c вже є функція int main, а в ній єдина дія - return 0. Видаляємо цей рядок (ми не збираємося повертати жодних значень), змінюємо тип функції на void (з тієї ж причини), і пишемо нескінченний цикл:
#include "stm32f10x_conf.h" 

void main()
{
while(1)
{
}
}

### Запускаємо модуль GPIO Порти введення-виводу в STM32 називаються GPIO - General Purpose Input/Output. Тому ми підключали бібліотеку stm32f10x_gpio.c. Однак це не все, що нам потрібно, трохи теорії: Вся периферія на кристалі за замовчуванням відключена, і від харчування і від тактової частоти. Щоб увімкнути, потрібно подати сигнал тактування. Цим управляє модуль RCC, а для роботи з ним існує файл stm32f10x_rcc.c. Модуль GPIO висить на шині APB2. Є ще AHB (аналог шини процесор-північний міст) і APB1 (так само як і APB2 - аналог шини північний міст-південний міст). Тому перше, що нам потрібно зробити – увімкнути тактування модуля GPIOC. Це модуль, який відповідає за PORTC; є ще GPIOA, GPIOB і таке інше. Робиться це так: RCC\_APB2PeriphClockCmd(RCC\_APB2Periph_GPIOC, ENABLE); Все просто - викликаємо функцію подачі тактового сигналу з шини APB2 на GPIOC модуль, і тим самим включаємо цей модуль. Звичайно, це робимо на самому початку функції void main. Тут - лише основи, необхідних розуміння. У мене також є набагато більше [детальніша стаття про модуль GPIO](/index.php/stm32-%e2%86%92-%d0%bf%d0%be%d1%80%d1%82%d1%8b-gpio / "STM32 → Порти GPIO"). ### Настроюємо модуль GPIOC Залишилося зовсім небагато, потрібно налаштувати модуль GPIOC. Встановлюємо ніжку на вихід (ще буває вхід та альтернативні функції), налаштовуємо різкість фронтів (з метою ЕМ сумісності), вихідний драйвер (пуш-пул або відкритий джерело). Це робимо одразу після ініціалізації порту. GPIO\_InitTypeDef GPIO\_InitStructure; GPIO\_InitStructure.GPIO\_Speed ​​= GPIO\_Speed\_2MHz; GPIO\_InitStructure.GPIO\_Mode = GPIO\_Mode\_Out_PP; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_8; GPIO\_Init(GPIOC, &GPIO\_InitStructure); Ну все, після цього ніжка PC8 буде працювати як пуш-пул вихід з відносно плавними фронтами ( максимальна частотаперемикання 2МГц. Гострі фронти – це 50МГц). Плавність фронтів оком не помітимо, а на осцилограф це видно. ### Включаємо світлодіод Викликаємо функцію GPIO\_WriteBit(GPIOC, GPIO\_Pin\_8, Bit\_SET); Світлодіод увімкнеться. ### Включаємо-вимикаємо його в циклі У циклі while(1) пишемо код включення, паузи, вимикання і знову паузи:

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);  for(i=0; i<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
for(i=0; i<1000000; i++);

Таким чином, повністю весь файл main.c виглядає так:

#include "stm32f10x_conf.h"

void main()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed ​​= GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOC, &GPIO_InitStructure);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);

int i;
while(1)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
for(i=0; i<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET); for(i=0; i<1000000; i++); } }

## 7. Запускаємо! Підключаємо плату STM32VLDiscovery до комп'ютера через microUSB, натискаємо IAR кнопку Download and Debug. Програма заливається в мікроконтролер (можна помітити віконце з прогрессбаром, яке швидко закривається - настільки малий розмір програми), і починається налагодження. IAR зупиняється на першій інструкції коду (це досить зручно при налагодженні), потрібно запустити його Go Go. Як завжди, ви можете скачати архів з проектом GPIO. На щастя, цей проект можна зберегти і далі використовувати його як шаблон, щоб не проводити все налаштування заново. Весь цикл: 1. Порти введення-виводу (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - з нуля до RTOS. 2: Таймер і переривання") (/index.php/stm32-from_zero_to_rtos-3_timer_out - з нуля до RTOS. 3: Виходи таймера")

](/index.php/stm32-from_zero_to_rtos-4_exti_nvic/ “STM32 - з нуля до RTOS. 4: Зовнішні переривання та NVIC”) 5. Ставимо FreeRTOS