Починаємо вивчати STM32 або Управляємо світлом по-розумному. Починаємо вивчати STM32 або Управляємо світлом по-розумному Визначення адрес спецреєстрів

Головна / Оптимізація роботи

Нещодавно колега мене підсадив на ідею створення розумного будинку, я навіть встиг замовити десятки різних датчиків. Постало питання про вибір Мікроконтролера(Далі МК) або плати. Після деяких пошуків знайшов кілька варіантів. Серед них були і Arduino(включаючи його клони, один із яких собі замовив заради того, щоб просто побалуватись) і LaunchpadАле все це надмірно і громіздко (хоча в плані програмування набагато простіше, але тему холіварів піднімати не буду, у кожного свої смаки). У результаті вирішив визначатися не з готовою платою, а взяти лише МК і робити все з нуля. У результаті вибирав між Atmel ATtiny (2313), Atmel ATmega(вирішив відмовитися тому, що не зміг знайти за адекватні гроші), STM32(Cortex на ядрі ARM). З тінькою я вже встиг побалуватись, так що взяв собі STM32VL-Discovery. Це можна назвати вступом до циклу статей з STM32. Обмовлюся одночасно, автором більшості цих статей буду не я, т.к. сам тільки пізнаю, тут я публікую їх насамперед для себе, щоб зручніше було шукати якщо щось забуду. І так поїхали!

Загальні відомості

Мікроконтролерисімейства STM32містять у своєму складі до семи 16-розрядних портів введення-виведення з іменами від PORTA до PORTG. У конкретній моделі мікроконтролерабез винятків доступні всі висновки портів, загальна кількість яких залежить від типу корпусу і обумовлено DataSheet на відповідну підродину.

Для включення порту x необхідно попередньо підключити його до шини APB2 установкою відповідного біта IOPxEN в регістрі дозволу тактування периферійних блоків RCC_APB2ENR:

RCC->APB2ENR | = RCC_APB2ENR_IOPxEN; // Дозволити тактування PORTx.

Управління портами STM32здійснюється за допомогою наборів із семи 32-розрядних регістрів:

  • GPIOx_CRL, GPIOx_CRH- задають режими роботи кожного з бітів порту як вход або вихід, визначають конфігурацію вхідних і вихідних каскадів.
  • GPIOx_IDR– вхідний регістр даних читання фізичного стану висновків порту x.
  • GPIOx_ODR- Вихідний регістр здійснює запис даних безпосередньо в порт.
  • GPIOx_BSRR– регістр атомарного скидання та встановлення бітів порту.
  • GPIOx_BSR- Регістр скидання бітів порту.
  • GPIOx_LCKR- Регістр блокування конфігурації висновків.

Режими роботи висновків GPIO

Режими роботи окремих висновків визначаються комбінацією бітів MODEyі CNFy регістрів GPIOx_CRLі GPIOx_CRH(Тут і далі: x-ім'я порту, y- номер біта порту).

GPIOx_CRL- регістр конфігурації висновків 0...7 порту x:

Структура регістру GPIOx_CRHаналогічна структурі GPIOx_CRLі призначена керувати режимами роботи старших висновків порту (біти 8...15).

Біти MODEy зазначених регістрів визначають напрямок виведення та обмеження швидкості перемикання в режимі виходу:

  • MODEy = 00:Режим входу (стан після скидання);
  • MODEy = 01:Режим виходу, максимальна швидкість – 10МГц;
  • MODEy = 10:Режим виходу, максимальна швидкість – 2МГц;
  • MODEy = 11:Режим виходу, максимальна швидкість – 50МГц.

Біти CNF задають конфігурацію вихідних каскадів відповідних висновків:

у режимі входу:

  • CNFy = 00:Аналоговий вхід;
  • CNFy = 01:Вхід у третьому стані (стан після скидання);
  • CNFy = 10:Вхід з резистором, що притягує, pull-up (якщо PxODR=1) або pull-down (якщо PxODR=0);
  • CNFy = 11:Зарезервовано.

у режимі виходу:

  • CNFy = 00:двотактний вихід загального призначення;
  • CNFy = 01:Вихід із відкритим стоком загального призначення;
  • CNFy = 10:Двотактний вихід із альтернативною функцією;
  • CNFy = 11:Вихід із відкритим стоком з альтернативною функцією.

З метою підвищення стійкості до перешкод всі вхідні буфери містять у своєму складі тригери Шмідта. Частина висновків STM32Обладнані захисними діодами, з'єднаними із загальною шиною і шиною живлення, позначені в данихдодатках як FT (5V tolerant) - сумісні з напругою 5 вольт.

Захист бітів конфігурації GPIO

Для захисту бітів у регістрах конфігурації від несанкціонованого запису STM32передбачено регістр блокування налаштувань GPIOx_LCKR
GPIOx_LCKR- Регістр блокування налаштувань виведення порту:

Для захисту налаштувань окремого виведення порту необхідно встановити відповідний біт LCKy. Після чого здійснити послідовний запис до розряду LCKKзначень "1" - "0" - "1" та дві операції читання регістру LCKR, які у разі успішного блокування дадуть для біта LCKKзначення "0" та "1" . Захист настроювальних бітів збереже свою дію до перезавантаження мікроконтролера.

Файл визначень для периферії мікроконтролерів STM32 stm32f10x.h визначає окремі групи регістрів, об'єднані загальним функціональним призначенням (зокрема і GPIO), як структури мови Сі, а самі регістри як елементи цієї структури. Наприклад:

GPIOC->BSRR– регістр BSRR установки/скидання порту GPIOC.
Скористаємося визначеннями із файлу stm32f10x.h для ілюстрації роботи з регістрами введення-виведення мікроконтролера STM32F100RBвстановленого у стартовому наборі STM32VLDISCOVERY:

#include "stm32F10x.h" u32 tmp; int main (void) (RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Дозволити тактування PORTC. GPIOC->CRH |= GPIO_CRH_MODE8; // Виведення світлодіода LED4 PC8 на вихід. GPIOC->CRH &=~GPIO_CRH_CNF8; на PC8 GPIOC->CRH |= GPIO_CRH_MODE9;// Виведення світлодіода LED3 PC9 на вихід.GPIOC->CRH &=~GPIO_CRH_CNF9;// Двотактний вихід на PC9. PA0 - на вхід // Заблокувати налаштування висновків PC8, PC9 GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9|GPIO_LCKR_LCKK; KR_LCK9|GPIO_LCKR_LCKK;tmp=GPIOC-> LCKR;tmp=GPIOC->LCKR;

Запис та читання GPIO

Для запису та читання портів призначені вхідний GPIOx_IDRта вихідний GPIOx_ODRрегістри даних.

Запис у вихідний регістр ODRпорту налаштованого на висновок здійснює встановлення вихідних рівнів всіх розрядів порту відповідно до значення, що записується. Якщо висновок налаштований як вхід з резисторами, що підтягують, стан відповідного біта регістра ODRактивує підтяжку виведення до шини живлення (pull-up, ODR=1) або до загальної шині мікроконтролера (pull-down, ODR=0).

Читання регістру IDRповертає значення стану висновків мікроконтролера налаштованих як входи:

// Якщо натиснути кнопку (PA0=1), встановити біти порту C, інакше скинути. if (GPIOA->IDR & GPIO_IDR_IDR0) GPIOC->ODR=0xFFFF; else GPIOC-> ODR = 0x0000;

Скидання та встановлення бітів порту

Для атомарного скидання та встановлення бітів GPIOу мікроконтролерах STM32призначений регістр GPIOx_BSRR. Традиційний для архітектури ARMспосіб управління бітами регістрів, що не вимагає застосування операції типу "читання-модифікація-запис"дозволяє встановлювати та скидати біти порту простим записом одиниці в біти установки BS (BitSet) та скидання BR (BitReset) регістра BSRR. При цьому запис у регістр нульових бітів не впливає на стан відповідних висновків.

GPIOx_BSRR– регістр скидання та встановлення бітів порту:

GPIOC->BSRR=GPIO_BSRR_BS8|GPIO_BSRR_BR9; // Запалити LED4 (PC8), погасити LED3. GPIOC->BSRR=GPIO_BSRR_BS9|GPIO_BSRR_BR8; // Запалити LED3 (PC9), погасити LED4.

Альтернативні функції GPIOта їх перепризначення (remapping)
Практично всі зовнішні ланцюги спеціального призначення STM32(включаючи висновки для підключення кварцових резонаторів, JTAG/SWDі так далі) можуть бути дозволені на відповідних висновках мікроконтролера, або відключені від них для можливості їх використання як висновки загального призначення. Вибір альтернативної функції виведення здійснюється за допомогою регістрів із префіксом "AFIO”_.
Крім цього регістри AFIO _ дозволяють вибирати кілька варіантів розташування спеціальних функцій на висновках мікроконтролера. Це зокрема стосується висновків комунікаційних інтерфейсів, таймерів (реєстри AFIO_MAPR), висновків зовнішніх переривань (реєстри AFIO_EXTICR) і т.д.

§> Загальні питання. Змінні оголошені користувачем.

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


Основний об'єкт програмування для класичного Сі – змінна. Це може бути одиночна або група особливо пов'язаних змінних, наприклад, масив або структура. Насправді змінна є деяке сховище для числа, що має своє унікальне ім'я і допустимий діапазон значень, виходити за межі якого вкрай небажано. І перше, що ми повинні зробити, перш ніж почати використовувати ім'я змінної в тексті програми, це познайомити програму з її властивостями. У мові Сі цей процес називається оголошенням змінної.

Навіщо треба оголошувати змінні?

Хоча мова Сі і абстрактний, використовуваний розробником мікроконтролер, як правило, цілком конкретний і має свій адресний простір пам'яті із заданими властивостями, де і зберігатиметься змінна, що оголошується. Оголошення, крім присвоєння змінної імені, змушує компілятор розмістити її за конкретною адресою в пам'яті мікроконтролера (за якою саме нас здебільшого не цікавить).

Як потрібно оголошувати змінні?

Правило для оголошення можна формулювати так: перш ніж ми вперше вживемо ім'я змінної в тексті нашої програми, необхідно розмістити її в наступному форматі:

Type name; // Змінна з ім'ям "name" та типом "type".

Тут: type – так званий ідентифікатор типу змінної із певного набору стандартних типів;
name - довільне ім'я змінної, аби воно не починалося з цифри, складалося лише з латинських символів, і не збігалося зі службовими словами мови Сі (список яких не такий великий, щоб зіткнутися з такою ситуацією треба насправді постаратися).

Що таке ідентифікатор типу та навіщо його згадувати?

Для зберігання змінної мікроконтролер використовує осередки пам'яті, розмір яких визначається його розрядністю. Так наприклад, мікроконтролери сімейства AVR - 8-розрядні, а значить для зберігання даних використовують комірки пам'яті розміром один байт, які здатні зберігати 256 різних числових значень. Якщо очікувані значення змінної можуть перевищити цю кількість, то її зберігання знадобиться дві чи більше осередків пам'яті. Оскільки Сі, строго кажучи, не уявляє які значення ми плануємо надавати змінної, то просить нас вказати її тип, який якраз і визначає припустимий діапазон значень. Це необхідно щоб не зарезервувати за нею надлишковий або неприпустимо малий об'єм пам'яті, а також попереджати нас при спробі привласнити занадто велике значення змінної, не здатної його зберегти. Для 8-розрядних мікроконтролерів найбільш часто вживані цілочисленні типи даних:

Здатні зберігати лише позитивні значення (беззнакові):
unsigned char - займає один байт пам'яті, значення 0...255
unsigned int - два байти, значення 0...65535
unsigned long - чотири байти, від 0 до (2^32)-1
здатні зберігати значення зі знаком (знакові):
signed char - займає один байт пам'яті, від -128...127
signed int - два байти, значення -32768...32767
signed long - вимагає чотири байти, значення від -(2^31) до (2^31)

Ключове слово "unsigned" (беззнакове), взагалі кажучи, можна не вживати, оскільки в Сі за умовчанням тип, для якого не зазначена ця ознака, вважається беззнаковим.
Для роботи з дробовими числами в Сі передбачені типи з плаваючою точкою:

Float – 32 біти, значення від ±1.18E-38 до ±3.39E+38
double – 32 (±1.18E-38…±3.39E+38) або 64 біти (±2.23E-308…±1.79E+308) залежно від налаштувань компілятора.

Примітка: розмір пам'яті для збереження змінних зазначених типів та діапазон допустимих значень може незначно змінюватись в залежності від середовища розробки або сімейства мікроконтролерів.

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

Наприклад:

Int A=100; // Змінна з ім'ям "А" типом int та початковим значенням рівним 100.

Практичний приклад: нехай планується написати програму, що блимає світлодіодом 5 разів. Для підрахунку числа миготінь буде потрібна змінна, значення якої, очевидно ніколи не буде негативним і не вийде за межі діапазону від 0 до 255, а значить в даному випадку буде достатньо використовувати однобайтовий тип char:

§> Область видимості змінної.

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

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

§> Область розміщення змінної.

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


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

Спочатку корисно знати, що змінні, оголошені користувачем без використання спеціальних ключових слів типу _eeprom або _flash, розміщуються в ОЗУ мікроконтролера, у вигляді одного або декількох осередків статичної пам'яті SRAM. У процесі роботи вони періодично копіюються в швидку реєстрову пам'ять РОН, яка безпосередньо взаємодіє з арифметично-логічним блоком АЛП мікроконтролера.
Питання розміщення змінних всередині ОЗП, як правило, цікаві лише в контексті швидкодії програми.

§> Регістри спеціального призначення мікроконтролера SFR.

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

Управління та контроль роботи мікроконтролера та його окремих внутрішніх модулів здійснюється шляхом запису та читання спеціальних осередків-регістрів у службовій області пам'яті ОЗУ - регістрів спеціального призначення (Special Function Register, далі просто SFR).

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

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

На початку будь-якої програми на Сі ми можемо бачити рядки типу:

#include "file1.h" // Включити до коду вміст файлу "file1.h".

#include - це директива (вказівка), що змушує середовище розробки помістити у це місце програми вміст файлу з ім'ям file1.h. Файли з розширенням.h називаються заголовками або h-файлами. Розробник може створювати власні h-файли та поміщати їх, враховуючи вміст, у будь-яке місце програми. Однак, щоб познайомити програму з SFR для даного типу мікроконтролера, необхідно підключати конкретні заголовні файли. Їх імена і кількість залежить від конкретного середовища розробки і типу мікроконтролера, що використовується, так, наприклад, в IAR для Atmega64 достатньо прописати рядки:

#include "iom64"
#include "inavr.h"

Після включення в текст необхідних h-файлів програма буде пізнавати імена SFR, що згадуються в ній, наприклад, регістр статусу мікроконтролера AVR з ім'ям SREG, буфер прийому/передачі модуля UART - UDR і так далі.

Заготівля програми для IAR, яка нічого не робить, але вже не "лається" на імена регістрів спеціального призначення мікроконтролера Atmega16, має виглядати так:

#include "iom16.h"
#include "inavr.h"
unsigned char ChisloMiganiy=0;
void main (void)
{
// Тут ми розмістимо програму, яка використовує змінну ChisloMiganiy
// та будь-які регістри Atmega16, імена яких прописані у файлі iom16.h.
}

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

§> Огляд стандартних операцій з регістрами.

Настав час перейти до більш серйозних операцій над регістрами та програмними змінними. Управління роботою мікроконтролера найчастіше зводиться до наступного простого набору з його регістрами:

1. Запис до регістру необхідного значення.
2. Читання значення регістру.
3. Встановлення в одиницю необхідних розрядів регістру.
4. Скидання розрядів регістру в нуль.
5. Перевірка розряду на логічну одиницю чи логічний нуль.
6. Зміна логічного стану розряду регістру на протилежний.

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

A = 16; // Присвоїти змінної A значення 16;
A = B; // Вважати значення змінної B і присвоїти це значення змінної A;
A = B+10; // Вважати значення змінної B, додати до ліченого значення 10, результат присвоїти змінної A (значення змінної B при цьому не змінюється).

§> Запис та читання регістрів.

З розглянутих прикладів видно, що оператор присвоювання сам собою вирішує два перші завдання — запис і читання значень регістрів. Наприклад, для відправки мікроконтролером AVR байта по шині UART достатньо записати його в передавальний регістр з ім'ям UDR:

UDR = 8; // Відправити UART число 8;

Щоб отримати прийнятий за UART байт, достатньо вважати його з регістру UDR:

§> Встановлення бітів регістрів.

Мова Сі не має у своєму складі команд безпосереднього скидання або встановлення розрядів змінної, проте присутні побітові логічні операції "І" та "АБО", які успішно використовуються для цих цілей.
Оператор побітової логічної операції "АБО" записується у вигляді вертикальної риси - "|" і може виконуватися між двома змінними, а також між змінною та константою. Нагадаю, що операція "АБО" над двома бітами дає в результаті одиничний біт, якщо хоча б один з вихідних бітів знаходиться в стані одиниці. Таким чином для будь-якого біта логічне "АБО" з "1" дасть в результаті "1", незалежно від стану цього біта, а "АБО" з логічним "0" залишить в результаті стан вихідного біта без зміни. Ця властивість дозволяє використовувати операцію "АБО" для встановлення N-ого розряду в регістрі. Для цього необхідно обчислити константу з одиничним N-ним бітом за формулою 2^N, яка називається бітовою маскою і виконати логічне "АБО" між нею та регістром, наприклад для встановлення біта №7 у регістрі SREG:

(SREG | 128) - це вираз зчитує регістр SREG і встановлює в ліченому значенні сьомий біт, далі досить змінене значення знову помістити в регістр SREG:

SREG = SREG | 128; // Встановити біт №7 регістру SREG.

Таку роботу з регістром прийнято називати "читання - модифікація - запис", на відміну простого присвоєння вона зберігає стан інших бітів без назви.
Наведений програмний код, встановлюючи сьомий біт у регістрі SREG, виконує цілком осмислену роботу - дозволяє мікроконтролеру обробку програмних переривань. Єдиний недолік такого запису - в константі 128 не легко вгадати встановлений сьомий біт, тому маску для N-ного біта записують в наступному вигляді:

(1<

SREG = SREG | (1<<7);

Або ще простіше з використання короткої форми запису мови Сі:

SREG | = (1<<7);

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

§> Скидання бітів у регістрах.

Ще одна логічна операція мови Сі - побітове "І" записується у вигляді символу "&". Як відомо, операція логічного "І", стосовно двох біт дає одиницю тоді і тільки тоді, коли обидва вихідні біти мають одиничне значення, це дозволяє застосовувати її для скидання розрядів у регістрах. При цьому використовується бітова маска, в якій всі розряди поодинокі, крім нульового на позиції, що скидається. Її легко отримати з маски із встановленим N-ним бітом, застосувавши до неї операцію побитного інвертування:
~(1<

SREG = SREG & (~ (1<<7)); или кратко: SREG &= ~ (1<<7);

У згаданому раніше заголовному файлі для конкретного мікроконтролера наведено стандартні імена розрядів регістрів спеціального призначення, наприклад:

#define OCIE0 1

Тут #define – вказівка ​​компілятора замінювати у тексті програми поєднання символів "OCIE0" на число 1, тобто стандартне ім'я біта OCIE0, який входить до складу регістру TIMSK мікроконтролера Atmega64 на його порядковий номер у цьому регістрі. Завдяки цьому установку біта OCIE0 у регістрі TIMSK можна наочніше записувати так:

TIMSK | = (1<

Встановлювати або скидати кілька розрядів регістру одночасно можна, поєднуючи бітові маски у виразах оператором логічного "АБО":

PORTA |= (1<<1)|(1<<4); // Установить выводы 1 и 4 порта A в единицу;
PORTA&=~((1<<2)|(1<<3)); // Выводы 2 и 3 порта A сбросить в ноль.

Приклад використання з регістрами, визначеними у CMSIS:

DAC0-> CTRL | = DAC_CTRL_DIFF; // Установка
DAC0->CTRL &= ~DAC_CTRL_DIFF; //скидання

§> Перевірка розрядів регістру на нуль та одиницю.

Регістри спеціального призначення мікроконтролерів містять у своєму складі безліч бітів-ознак, так званих "прапорів”, що повідомляють програму про поточний стан мікроконтролера та його окремих модулів. скинутий даний розряд у регістрі.Таким виразом може служити логічне "І" між регістром і маскою з встановленим розрядом N на позиції біта, що перевіряється:

(REGISTR & (1<

Наведений вираз можна використовувати в умовному операторі if (вираз) або операторі циклу while (вираз), які відносяться до групи логічних, тобто сприймають як аргументи значення типу істина та брехня. Оскільки мова Сі, наводячи числові значення до логічних, будь-які числа не рівні нулю сприймає як логічну істину, значення (REGISTR & (1<Якщо виникає необхідність при встановленому біті N отримати для нашого вираження логічне значення «брехня», достатньо доповнити його оператором логічної інверсії у вигляді знака оклику - !(REGISTR & (1<

While (!(UCSRA & (1<

Тут при скинутому биті UDRE вираз (UCSRA & (1<!(UCSRA & (1<

§> Зміна стану біта регістру на протилежне.

Цю, з дозволу сказати, проблему з успіхом вирішує логічна операція побитого "ВИКЛЮЧНОГО АБО" і відповідний їй оператор Сі, що записується у вигляді символу "^". Правило "виключає або" з двома бітами дає "істину" тоді і тільки тоді, коли один з бітів встановлений, а інший скинутий. маски без зміни та інвертує розташовані навпроти одиничних, наприклад, якщо: reg=b0001 0110 та mask=b0000 1111, то reg^mask=b0001 1001. У такий спосіб можна змінювати стан світлодіода, підключеного до п'ятого біта порту A:

#define LED 5 // Замінювати у програмі поєднання символів LED на число 5 (виведення світлодіода).

PORTA ^=(1<< LED); // Погасить светодиод, если он светится и наоборот.

§> Арифметика та логіка мови Сі.

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


Для більш докладного знайомства з операціями над змінними та мовою Сі загалом рекомендую книгу "Мова програмування Сі" Б. Керніган, Д. Рітчі.

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

§> Перетворення типу виразу перед присвоєнням змінної.

У першому розділі ми звертали свою увагу на необхідність явної вказівки типу змінної, що оголошується. Це дозволяє компілятору зарезервувати за нею потрібну кількість адресного простору та визначити діапазон значень, які вона здатна зберігати. Тим не менш, ми не застраховані він того, що в процесі виконання програми відбудеться спроба записати в змінну значення понад гранично допустиме. У найгірших випадках компілятор видасть нам повідомлення про можливу помилку. Наприклад, за бажання записати в змінну типу unsigned char (діапазон від 0 до 255) число 400:

Unsigned char a=400; // видасть повідомлення типу "integer conversion resulted in truncation"

Компілятор попереджає нас про те, що відбулася спроба записати числове значення, що вимагає для зберігання два байти (400 це 1 у старшому байті і 144 в молодшому) в однобайтову змінну. Проте в тих випадках, коли вираз містить змінні, і компілятор міг би помітити можливу втрату даних, він звільняє себе від цього обов'язку, наприклад:

Unsigned char x=200, y=200;
x = x + y;

При такому варіанті, незважаючи на те, що значення вираз (x+y) так само дорівнює 400, ніяких попереджень з боку компілятора не буде. А в змінну x запишеться лише молодший байт числа 400, тобто 144. І тут компілятор важко чимось дорікнути, адже замість явно проініціалізованої змінної у виразі може бути використаний, наприклад, приймальний регістр шини UART, в якому може бути будь-яке значення, прийняте від зовнішнього устрою.
Інший приклад у цьому дусі – присвоєння дробового значення змінної цілого типу:

Float a = 1.5; // Оголошено змінну з плаваючою точкою.

b = a * b; // Очікується, що змінну b буде записано значення 4,5.

У результаті змінної b збережеться лише ціла частина результату a*b – число 4.

§> Перетворення результату виразу до типу найбільш точної змінної у виразі.

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

Float a = 1.5; // Оголошено змінну a з плаваючою точкою.
char b = 3; // Оголошено цілочисленну змінну.

У виразі (a*b) змінна float a має вищий тип, оскільки може зберігати будь-яке ціле значення з діапазону 0…255 типу char. Результат виразу (a*b) матиме тип float.
Типовий приклад несподіванки для цього випадку – спроба отримати дрібне число поділом двох цілих:

Char a = 3; // Оголошено цілочисленну змінну.
char b = 4; // Оголошено цілочисленну змінну.
float c; // Оголошено змінну "c" з плаваючою точкою для збереження результату.
c=a/b; // Очікується, що "c" дорівнюватиме 0,75 (¾).

На відміну від попереднього прикладу, результат записується в змінну здатну зберігати числа з плаваючою точкою, однак компілятор відповідно до правила приведення, отримавши в результаті поділу число 0,75 призводить його до типу цілих операндів, відкинувши дробову частину. В результаті змінну "c" буде записано нуль.
Більш реалістичний приклад із життя - розрахунок вимірюваної напруги з вихідного коду АЦП:

Int ADC; // Двобайтова цілочисленна змінна зберігання коду АЦП.
float U; // Змінна з плаваючою точкою для збереження значення напруги.
U = ADC * (5/1024); // Розрахунок напруги.

Тут втрачено на увазі те, що константа в Сі, як і будь-яка змінна, теж має свій тип. Його бажано вказувати явно чи, використовуючи відповідну форму запису. Константи 5 і 1024 записані без десяткової точки і будуть сприйняті мовою Сі як цілочисленні. Як наслідок, результат виразу (5/1024) також буде приведений до цілого – 0 замість очікуваного 0,00489. Це не було б при записі виразу у форматі (5.0/1024).
Наведених помилок також можна уникнути, використовуючи оператор явного приведення типів виразів мови Сі, який записується у вигляді назви типу, укладеного в круглі дужки і впливає на вираз, що стоїть після нього. Цей оператор наводить результат виразу до явно зазначеного типу, незважаючи на типи його операндів:

C = (float) a/b; // Очікується, що "c" дорівнюватиме 0,75 (¾);
U = ADC * ((float) 5/1024); // Розрахунок напруги.

§> Призначення функцій.

Ще стародавні програмісти звернули свою увагу на один цікавий факт – найчастіше програма змушена кілька разів виконувати рівно одну й ту саму послідовність дій. Саме тоді народилася ідея при досить великому наборі таких дій та їх повторів, з метою економії програмної пам'яті, оформляти їх у вигляді окремої групи, а потім, за необхідності, просто відправляти програму на її виконання. Такий відокремлений шматок коду Сі якраз і називається функцією. Сама назва терміна "функція" споконвічно відображає іншу властивість деяких функцій - здатність (подібно до функцій математичним) перетворювати за заданим алгоритмом деякі вхідні дані. Але це трохи пізніше.

Інше призначення функції, що повністю відбиває її назву - це виділення в окрему групу дій пов'язаних однією спільною метою, наприклад, функція ініціалізації портів або опція клавіатури. Це і є одним із додаткових призначень функції.
Такі функції можуть викликатися програмою лише один раз. Навіщо тоді вони потрібні? Для обґрунтування такого підходу в літературі часто наводиться фраза невідомого, але, мабуть, дуже авторитетного давньоримського програміста: "Поділяй і володарюй!" набір окремих, розрізнених за призначенням шматків коду.
Узагальнюючи сказане, можна сформулювати формальні передумови створення функцій у програмі, це:

1. Наявність однакових, досить великих і багаторазово повторюваних наборів дій.
2. Бажання структурувати програму як окремих блоків із загальним функціональним призначенням.

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

§> Структура та оформлення функцій.

У будь-якій функції структурно легко виділити дві складові: заголовок і тіло функції.
Заголовок це найперший рядок будь-якої функції виду:

Тип вихідної змінної Ім'я функції (Типи вхідних змінних та їх імена через кому)

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

Void ім'я функції (void)

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

Initialization ();

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

Void initialization (void)
{
DDRA = 0xFF; // PORTA на вихід.
DDRB | = (1<<0)| (1<<3)| (1<<4); // PB0, PB3, PB4 на выход.
DDRC = 0xF0; // Старший зошит PORTC на вихід.
}

Проініціалізувавши напрямок висновків портів A, B і С, повернеться до наступного після її виклику рядка.
Для початку також важливо знати, що текст програми кожна функція повинна бути розташована окремо, тобто одна функція не може знаходитися всередині іншої або частково накладатися на неї.

§> Обробка параметрів функцією.

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

Таке оформлення заголовка означатиме, що функція здатна приймати як параметри два числа типу char з іменами FrameLength і StopBit. Тепер при виклику функції компілятор не дозволить залишити круглі дужки порожніми і вимагатиме передачі конкретних значень через кому, наприклад:

InitUart (8, 2);

Після цього всередині функції змінним з іменами FrameLength і StopBit нададуться конкретні значення 8 і 2, які можна використовувати, наприклад, для налаштування довгої посилки модуля UART і кількості його стоп-бітів:

Void initUart (char FrameLength, char StopBit)
{
if (FrameLength==8) UCSR0C|=((1<<1)|(1<<2));
if (StopBit==2) UCSR0C|=(1<<3);
}

§> Спеціалізовані функції.

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

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

§> Загальна структура найпростішої програми. Ініціалізація, фону.

При розгляді програми лише на рівні мови Сі можна сказати, що вона починає свою роботу з першого рядка функції main (рядок 001 малюнку):

Структкра програми на Сі
Далі послідовно виконуються рядки 002, 003, 004, об'єднані однією загальною властивістю: програма проходить по них лише один раз, запускаючи мікроконтролер. Цю частину програми прийнято називати ініціалізаційною. Ініціалізаційна частина - законне місце для розміщення дій щодо підготовки периферії мікроконтролера до роботи із заданими параметрами - налаштування портів на вхід або вихід, початкової ініціалізації таймерів, завдання швидкості та формату кадру UART і так далі для всього, що планується використовувати надалі.

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

Розглянемо описану структуру програми на найпростішому прикладі. Нехай необхідно: відправляти символ UART по шині UART, поки кнопка на виведенні PA0 знаходиться в натиснутому стані (нульовий рівень сигналу). Програма в даному випадку (без зайвих процедур з придушення брязкоту кнопки та іншого) може виглядати так:

Void main (void)
{
PORTA | = (1<<0); // Притянуть вход кнопки PORTA.0 внутренним pull-up резистором.

UCSRB = (1<while (1)
{
if (! (PINA & (1<<0))) // Если кнопка нажата...
{
while(! (UCSRA & (1<UDR = "*"; // Відправити *.
}
// Інші команди фону:
00N
00N+1
...
}
}

Тут конструкція if(...), розташована у фоні програми проводить нескінченні опитування вхідного регістру PINA та перевірку виведення PA0 на наявність низького рівня. Далі виконуються інші дії фонового процесу, позначені рядками 00N, 00N+1 тощо.

Які чинники стосовно цієї програми визначають найважливіші параметри її роботи - надійність і швидкодія?

З прикладу видно, що частота опитування входу PA.0 визначається тривалістю виконання команд фону, адже перш ніж в черговий раз опитати кнопку, мікроконтролер повинен виконати наступні рядки 00N, 00N+1 і т. д. Очевидно, що надійність фіксації зовнішньої події (натискання на кнопку) у разі буде залежати від співвідношення тривалості впливу цієї події до періоду його детектування. Тривалість фону в даній програмі напевно буде набагато менше тривалості утримання кнопки, яке на практиці становить кілька десятків мілісекунд. Однак при розростанні фонової частини програми та малому часі зовнішнього впливу, надійність його відстеження у певний момент різко знизиться. Щоб цього не сталося, а також для зниження часу реакції програми на зовнішню подію, використовується система переривань.

§> Переривання.

Як працює механізм переривань? Дуже просто, особливо на рівні мови Сі!

В архітектуру мікроконтролерів AVR, втім як і будь-яких інших, на апаратному рівні закладена здатність відстежувати певні "цікаві стани заліза" і встановлювати при цьому відповідні біти-ознаки. Такі стани називаються умовами виникнення переривань, а встановлювані ознаки - прапорами переривань. мікроконтролер безперервно відстежує стан цих прапорів.При виявленні будь-якого встановленого прапора переривання, за умови, що воно дозволено включенням відповідного біта, а також встановлено біт глобального дозволу переривань (№7 в регістрі SREG для AVR), виконання основної частини програми буде тимчасово призупинено (перервано ).

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

Яка роль програміста у цьому процесі? Під час розробки на Сі вона зведена до мінімуму.
Частина дій, як відстеження прапорів переривань реалізовані на апаратному рівні. Іншу частину, наприклад, захист від змін в обробнику важливого для програми регістру статусу SREG, збереження адреси програми в стеку та багато іншого компілятор бере на себе.
Єдине, в чому залишається необхідність:

1. Дозволити використання переривань у програмі.
2. Дозволити виклик переривання, що цікавить нас, спеціальним бітом у відповідному регістрі. Яким саме та де підкаже опис на мікроконтролер.
3. Створити умови виникнення переривання, наприклад, якщо це переповнення таймера, то банально запустити його. Якщо це переривання зі зміни стану зовнішнього виведення, то задати потрібні умови для цього (фронт, зріз або нульовий рівень).
4. Розмістити у програмі обробник переривання, оформивши його відповідно до вимог компілятора.

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

Перенесемо кнопку виведення PD.2 з альтернативною функцією INT0. В ініціалізаційній частині програми дозволимо переривання глобально та INT0 безпосередньо. Мікроконтролер за замовчуванням налаштований на формування переривання INT за низьким рівнем вхідного сигналу, тому додаткових налаштувань не потрібно. Залишається оголосити за межами функції main обробник INT0, що відправляє в UART символ *:

Void main (void)
{
PORTD | = (1<<2); // Притянуть вход кнопки PORTD.2 внутренним pull-up.
UBRRL = 51; // Швидкість UART - 9600 bps.
UCSRB = (1<SREG | = (1<<7); // Разрешить прерывания.
GICR|=(1<while (1)()
}

#pragma vector=INT0_vect // Обробник переривання INT0/
__interrupt void INT0_INTPT()
{
if (! (PIND & (1<<2))) {while(! (UCSRA & (1< }

Тут обробник переривання оголошено форматі компілятора IAR. Принципово у ньому лише ім'я вектора переривання - INT0_vect, компілятор замінює його адресу пам'яті програм, який передається виконання програми у разі даного переривання. Ім'я самого обробника INT0_INTPT вибирається довільно. Назви векторів всіх можливих переривань для МК описані в h-файлах.

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

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

Взято із сайту
http://eugenemcu.ru/

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

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

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

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

Все це для мене на той момент здалося незбагненно складним, і я навіть прийшов у деякий сум'яття, але від реалізації поставленого завдання відмовлятися не збирався. Так я познайомився з сімейством мікроконтролерів STM32 і платою STM32F0-Discovery, після вивчення яких мені хотілося б створити свій девайс під потрібні мені цілі.

На мій великий подив, такого великого комьюніті, статей, прикладів, різних матеріалів по STM не було в такому ж достатку як для Arduino. Звичайно, якщо пошукати знайдеться безліч статей для початківців де описано, як і з чого почати. Але на той момент мені здалося, що все це дуже складно, не розповідалося багато деталей, цікавих для допитливого розуму новачка, речі. Багато статей хоч і характеризувалися як «навчання для найменших», але не завжди з їхньою допомогою виходило досягти необхідного результату, навіть із готовими прикладами коду. Саме тому я вирішив написати невеликий цикл статей з програмування на STM32 у світлі реалізації конкретного задуму: пульт керування освітленням у кімнаті.

Чому не AVR/Arduino?

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

По-перше, вирішальну роль відіграло відношення ціна-функціонал, різницю видно навіть між одним із найдешевших і найпростіших МК від ST і досить «жирною» ATMega:


Після того, що я побачив значні відмінності між ціною та можливостями AVR та STM32 – мною було прийнято рішення, що AVR використовувати у своїй розробці я не буду =)

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

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

По-четверте, люди, які займаються професійною розробкою, більше схильні до використання 32-розрядних МК, і найчастіше це моделі від NXP, Texas Instruments і ST Microelectronics. Та й мені можна було в будь-який момент підійти до своїх інженерів з відділу розробки і дізнатися про те, як вирішити те чи інше завдання і отримати консультацію з питань, що мене цікавлять.

Чому варто починати вивчення мікроконтролерів STM32 із використання плати Discovery?

Як ви вже зрозуміли, знайомство та вивчення мікроконтролера STM32 ми почнемо з Вами, шановні читачі, із використання плати Discovery. Чому саме Discovery, а чи не своя плата?

Що нам знадобиться для розробки, крім плати Discovery?

У роботі з платою Discovery нам знадобиться ще ряд незамінних речей, без яких ми не зможемо обійтися:

Приступимо до початкового настроювання та підготовки IDE до роботи!

Після того, як завантажується інсталяційний файл нашої IDE, можна приступати до встановлення. Дотримуючись вказівок інсталятора, проведіть процес установки. Після того, як скопіюються всі файли, необхідні для роботи, з'явиться вікно установника софтових пакетів для розробки Pack Installer. Цей установник містить низькорівневі бібліотеки, Middleware, приклади програм, які регулярно поповнюються і оновлюються.


Для початку роботи з нашою платою нам необхідно встановити низку пакетів необхідних для роботи та необхідно знайти мікроконтролер, з яким ми працюватимемо. Також можна скористатися пошуком вгорі вікна. Після того, як ми знайшли наш МК, клацаємо на нього і в другій половині вікна і нам необхідно встановити наступний перелік бібліотек:
  1. Keil::STM32F0xx_DFP- Повноцінний пакет програмного забезпечення для конкретного сімейства мікроконтролерів, що включає мануали, датташити, SVD-файли, бібліотеки від виробника.
  2. ARM::CMSIS– пакет Cortex Microcontroller Software Interface Standard, що включає повний набір бібліотек від ARM для підтримки ядра Cortex.
  3. Keil::ARM_Compiler– остання версія компілятора ARM.
Після встановлення потрібних паків можна перейти до налаштування IDE та нашого налагоджувача/програматора. Для цього нам необхідно відкрити головне вікно Keil та створити новий проект.


Для цього необхідно перейти до меню Project -> New uVision Projectта вибрати папку, в яку збережемо наш проект.

Після Keil запитає нас, який МК буде використовуватися в проекті. Вибираємо потрібний нам МК та натискаємо ОК.


І знову з'явиться, вже знайоме нам, вікно в якому ми можемо підключити модулі, що цікавлять нас, до проекту. Для нашого проекту знадобиться два модулі:
  1. Ядро бібліотеки CMSIS, В якому оголошено налаштування, адреси регістрів і багато іншого з того, що необхідно для роботи нашого МК.
  2. Startup-файл, який відповідає за початкову ініціалізацію МК при старті, оголошення векторів та обробників переривань та багато іншого.
Якщо всі залежності у тих, хто підключається, задоволені – менеджер буде нам сигналізувати про це зеленим кольором:


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

Для того, щоб налаштувати параметри проекту та налаштувати наш програматор потрібно правим кліком по Target 1 відкрити відповідне меню.


У головному меню проекту налаштовуємо параметр Xtalна значення 8.0 MHz . Цей параметр відповідає за частоту роботи кварцового осцилятора нашого МК:


Далі переходимо до налаштування нашого програматора/дебагера. Клацаємо в цьому ж вікні на вкладку Debugі вибираємо в полі Useпараметр ST-Link Debugger і переходимо в налаштування:


У налаштуваннях ми повинні побачити модель нашого ST-Link встановленого на платі, його серійний номер, версію HW та IDCODE МК, який будемо прошивати:

Для зручності можна налаштувати параметр, який відповідає за те, щоб МК скидався автоматично після перепрошивки. Для цього потрібно поставити галочку у полі Reset and Run.


Після цього потрібно налаштувати ще одну опцію, яка дозволить нам писати російськомовні коментарі до коду наших проектів. Натискаємо кнопку Configurationі в меню, що відкрилося в полі Encodingобираємо Російський Windows-1251 .


Всі. Наша IDE та програматор готові до роботи!

У Keil є зручний навігатор за проектом, в якому ми можемо бачити структуру проекту, необхідні для роботи довідкові матеріали, в тому числі ті, які ми вже завантажили до себе на комп'ютер до цього (схема Discovery, datasheet, reference manual), список функцій, використаних у проекті та шаблони для швидкої вставки різних мовних конструкцій мови програмування.


Перейменуємо папку в структурі проекту з Source Group 1 на App/User , таким чином позначивши те, що в даній папці у нас будуть розташовуватися файли програми користувача:


Додамо основний файл програми через навігатор проекту, виконавши команду Add New Item To Group “App/User” .


Необхідно вибрати із запропонованого списку C File (.c) та призначити йому ім'я main.c :


Створений файл автоматично додасться до структури проекту та відкриється у головному вікні програми.

Що ж, тепер ми можемо розпочати створення нашої програми.

Насамперед, необхідно підключити до нашого виконуваного файлу заголовний документ нашого сімейства мікроконтролерів. Додамо до файлу main.c рядки наступного змісту, дана програма змусити поперемінно моргати наші світлодіоди:

/* Заголовковий файл для нашого сімейства мікроконтролерів*/ #include "stm32f0xx.h" /* Тіло основної програми */ int main(void) ( /* Включаємо тактування на порту GPIO */ RCC->AHBENR |= RCC_AHBENR_GPIOCEN; /* Налаштовуємо режим роботи портів PC8 і PC9 в Output * / GPIOC -> MODER = 0x50000; / * Налаштовуємо Output type в режим Push-Pull * / GPIOC -> OTYPER = 0; = 0; while(1) ( /* Запалюємо світлодіод PC8, гасимо PC9 */ GPIOC->ODR = 0x100;<500000; i++){} // Искусственная задержка /* Зажигаем светодиод PC9, гасим PC8 */ GPIOC->ODR = 0x200; for (int i=0; i<500000; i++){} // Искусственная задержка } }
Після того, як ми написали нашу програму, настав час скомпілювати код і завантажити прошивку в наш МК. Щоб скомпілювати код і завантажити, можна скористатися даним меню:


Команда Build (або гаряча клавіша F7) скомпілює код, і якщо не було жодних помилок програмі виведе в лозі компіляції наступне повідомлення про те, що помилок та попереджень немає:


Команда Load (або гаряча клавіша F8) завантажить компільований код у наш МК і автоматично відправить його на виконання:


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


Ура! Перший крок у освоєнні мікроконтролерів STM32 ми зробили! У ми розберемо що таке бітові та логічні операції, як ними користуватися і дізнаємося про одну дуже корисну утилітку для роботи з МК, ну а поки що можемо насолоджуватися тим, як весело переморгуються світлодіоди на нашій платі Discovery.

Система тактування STM32.

Сьогодні мова піде про систему тактування мікроконтролерів STM 32. Якщо ви ще не знаєте, що таке такт, частота і взагалі не торкалися до цього системи тактування, . Хоча за цим посиланням і розглядається система тактування мікроконтролера AVR, поняття визначені в уроці за посиланням, застосовні і до системи тактування мікроконтролерів STM 32.

Отже, почнемо!

Розглядатимемо систему тактування на прикладі мікроконтролера STM 32F 303VCT 6, який встановлений у налагоджувальній платі STM 32 F 3 DISCOVERY .

Погляньмо на загальну структуру системи тактування:

Як ми бачимо, система тактування STM 32, на порядок складніша за систему тактування мікроконтролерів. AVR, незважаючи на те, щомалюнку відбито лише основна її частина.

Давайте розумітися!

Розглядати схему слід зліва направо. По-перше, ми маємо вибрати основне джерело тактування контролера. Вибиратимемо між HSI та HSE.

HSE-Зовнішній високочастотний генератор. Джерелом тактування йому служить зовнішній тактовий сигнал (Input frequency ), який як бачимо за схемою, то, можливо від 4 до 32 МГц. Це може бути кварцовий резонатор, тактовий генератор і таке інше.

HSI - Внутрішній високочастотний генератор. У мікроконтролерах STM 32 F 3 є RC ланцюжком із частотою 8МГц. Точність значно нижча від зовнішнього генератора HSE.

Кожен із даних джерел тактування може бути з'єднаний з PLL. Однак перед подачею на PLL сигнал HSI буде зменшений в 2 рази. Сигнал HSE у свою чергу може подаватися на PLL без змін, або бути зменшений у певну кількість разів, за бажанням користувача.

PLL Clock - Система Фазової Автопідстроювання Частоти (ФАПЧ). Дозволяє помножити вхідний сигнал HSI або HSE у необхідну кількість разів.

PLL сигнал може бути подано на системну шину, максимальна частота якої 72МГц. Або на системну шину може бути поданий сигнал HSE або HSI безпосередньо, тобто без перетворення PLL .

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

Для налаштування тактування можна вдатися до ручної редагування регістрів, або скористатися бібліотечними функціями. Ми скористаємось бібліотекою.

Налаштуємо нашу налагоджувальну плату STM 32 F 3 DISCOVERYпрацювати з тактовою частотою 72 МГц.

Створимо та налаштуємо проект у Keil uVision. .

Додамо наступний код:

#include "stm32f30x_gpio.h" #include "stm32f30x_rcc.h" void InitRCC() ( RCC_HSEConfig(RCC_HSE_ON); //Enable ->ACR |= FLASH_ACR_PRFTBE; FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)((uint8_t)0x02); RCC_PREDIV1Config(RCC_PREDIV1_Div1);//PREDIV 1 Divider = 1 RCC_PLLConfig(RCC_PLLSource_PREDIV1,RCC_PLLMul_9); SYSCLKConfig( RCC_SYSCLKSource_PLLCLK); // Set PLL як SYSCLK Soucre RCC_HSICmd (DISABLE);

#include "stm32f30x_gpio.h"

#include "stm32f30x_rcc.h"

void InitRCC ()

RCC_HSEConfig (RCC_HSE_ON); //Enable HSE

while (RCC_GetFlagStatus (RCC_FLAG_HSERDY) == RESET); //Waiting for HSE

//Set Flash latency

FLASH -> ACR | = FLASH_ACR_PRFTBE ;

FLASH -> ACR &= (uint32_t) ((uint32_t) ~ FLASH_ACR_LATENCY);

FLASH -> ACR | = (uint32_t) ((uint8_t) 0x02);

RCC_PREDIV1Config (RCC_PREDIV1_Div1); //PREDIV 1 Divider = 1

RCC_PLLConfig (RCC_PLLSource_PREDIV1, RCC_PLLMul_9); //Set PREDIV1 як source for PLL,And set PLLMUL=9

RCC_PLLCmd (ENABLE); //Enable PLL

while (RCC_GetFlagStatus (RCC_FLAG_PLLRDY) == RESET); //Waiting for PLL

RCC_SYSCLKConfig (RCC_SYSCLKSource_PLLCLK); //Set PLL as SYSCLK Soucre

RCC_HSICmd (DISABLE); //Disable HSI

int main (void)

RCC_ClocksTypeDef RCC_Clocks;

InitRCC();

RCC_GetClocksFreq (& RCC_Clocks);

NOP();

while (1 )

В основній функції main, оголошено структуру RCC _ ClocksTypeDef. Ця структура містить у собі поля, відбивають поточну тактову частоту певних елементів контролера.

Потім основною функцією викликається функція InitRCC ,яка налаштовує тактування контролера. Розглянемо її докладніше.

Командою RCC _ HSEConfig (RCC _ HSE _ ON ), ми включаємо HSE .На його включення потрібен час, тому необхідно почекати доки не буде встановлено прапор RCC _ FLAG _ HSERDY . Робимо ми це у циклі while (RCC _ GetFlagStatus (RCC _ FLAG _ HSERDY ) == RESET ) .

Потім ми робимо налаштування затримки флеш пам'яті. Це необхідно робити під час роботи системної шини на частотах понад 36 МГц!

Після налаштування затримки вибираємо розподільник PLL. Командою RCC _ PREDIV 1 Config (RCC _ PREDIV 1_ Div 1) ми встановлюємо ділника на 1. Командою RCC _ PLLConfig (RCC _ PLLSource _ PREDIV 1, RCC _ PLLMul _9 ) вибираємо HSE як джерело частоти для PLL і вибираємо множення у 9 разів. Залишається тільки включити PLL командою RCC _ PLLCmd (ENABLE ), та чекати на встановлення прапора RCC _ FLAG _ PLLRDY, У циклі while . Тим самим ми забезпечуємо необхідну тимчасову затримку для включення PLL. Після цього вибираємо PLL як джерело системної частоти SYSCLK командою RCC _ SYSCLKConfig (RCC _ SYSCLKSource _ PLLCLK ). Предділителі шин чіпати не будемо, тому шини AHB, APB 1, APB 2 працюватимуть на частотах 72,36 і 72 МГц відповідно.

Залишається лише вимкнути внутрішній RC ланцюжок командою RCC _ HSICmd (DISABLE ).

Після виконання функції InitRCC, в основному циклі прошивки заповнимо структуру RCC _ ClocksTypeDefщо дозволить нам дізнатися, чи правильно ми налаштували систему тактування. Робимо ми це командою RCC_GetClocksFreq (&RCC_Clocks).

Подивитися значення тактових частот контролера можна в режимі налагодження, встановивши точку зупинки на команді __ NOP () що означає порожню команду. Цю команду часто додають для зручності налагодження.

Підключаємо налагоджувальну плату STM32 F3 DISCOVERY, збираємо прошивку, прошиваємо плату і нарешті заходимо в режим налагодження, натиснувши кнопку Start /Stop debug session (Ctrl + F 5). Встановивши точку зупинки на функції __ NOP,і додавши структуру RCC _Clocks в Watch ,запускаємо виконання прошивки, натиснувши F 5. В результаті бачимо:

Частоти налаштовані правильно, і мікроконтролер тепер працює на частоті 72 МГц.

Отже, як Ви зрозуміли з сьогоднішнього уроку, система тактування STM 32 є досить потужною і гнучкою для задоволення потреб Ваших проектів. Витративши час на її налаштування - Ви досягнете чудових результатів!

Дякую за увагу! Ваші питання як завжди у коментарях!

Будь-яке копіювання, відтворення, цитування матеріалу або його частин дозволено лише за письмовою згодою адміністрації MKPROG.RU. Незаконне копіювання, цитування, відтворення переслідується згідно із законом!

Опубликовано 09.08.2016

Мікроконтролери STM32набувають все більшої популярності завдяки своїй потужності, досить різнорідній периферії, і своїй гнучкості. Ми почнемо вивчати, використовуючи бюджетну тестову плату, вартість якої не перевищує 2$ (у китайців). Ще нам знадобиться ST-Linkпрограматор, вартість якого близько 2.5$ (у китайців). Такі суми видатків доступні і студентам та школярам, ​​тому саме з такого бюджетного варіанту я і пропоную розпочати.


Цей мікроконтролер не є найпотужнішим серед STM32але й не найслабший. Існують різні плати з STM32, у тому числі Discoveryякі за ціною коштують близько 20$. На таких платах майже все те, що і на нашій платі, плюс програматор. У нашому випадку ми використовуватимемо програматор окремо.

Мікроконтролер STM32F103C8. Характеристики

  • Ядро ARM 32-bit Cortex-M3
  • Максимальна частота 72МГц
  • 64Кб Флеш пам'ять для програм
  • 20Кб SRAM пам'яті
  • Харчування 2.0 … 3.3В
  • 2 x 12-біт АЦП (0...3.6В)
  • DMA контролер
  • 37 входів/виходів толерантних до 5В
  • 4 16-розрядні таймери
  • 2 watchdog таймера
  • I2C – 2 шини
  • USART – 3 шини
  • SPI – 2 шини
  • USB 2.0 full-speed interface
  • RTC – вбудований годинник

На платі STM32F103C8 доступні

  • Виводь портів A0-A12, B0-B1, B3-B15, C13-C15
  • Micro-USBчерез який можна живити плату. На платі є стабілізатор напруги на 3.3В. Харчування 3.3 або 5В можна подавати на відповідні висновки на платі.
  • Кнопка Reset
  • Дві перемички BOOT0і BOOT1. Будемо використовувати під час прошивки через UART.
  • Два кварці 8МГц та 32768 Гц. У мікроконтролера є множник частоти, тому на кварці 8 МГц ми зможемо досягти максимальної частоти контролера 72МГц.
  • Два світлодіоди. PWR– сигналізує про подачу живлення. PC13– підключено до виходу C13.
  • Конектор для програматора ST-Link.

Отже, почнемо з того, що спробуємо прошити мікроконтролер. Це можна зробити за допомогою через USART, або за допомогою програматора ST-Link.

Завантажити тестовий файл для прошивки можна. Програма блимає світлодіодом на платі.

Прошивка STM32 за допомогою USB-Uart перехідника під Windows

У системній пам'яті STM32є Bootloader. Bootloader записаний на етапі виробництва та будь-який мікроконтролер STM32можна запрограмувати через інтерфейс USARTза допомогою USART-USB перехідника. Такі перехідники найчастіше виготовляють на базі популярної мікросхеми. FT232RL. Насамперед підключимо перехідник до комп'ютера і встановимо драйвера (якщо потрібно). Завантажити драйвера можна з сайту виробника FT232RL- ftdichip.com. Треба качати драйвера VCP(Virtual com port). Після встановлення драйверів на комп'ютері має з'явитися віртуальний послідовний порт.


Підключаємо RXі TXвиходи до відповідних висновків USART1мікроконтролера. RXперехідника підключаємо до TXмікроконтролера (A9). TXперехідника підключаємо до RXмікроконтролера (A10). Оскільки USART-USB має виходи живлення 3.3В подамо харчування на плату від нього.

Щоб перевести мікроконтролер у режим програмування, треба встановити висновки BOOT0і BOOT1у потрібний стан та перезавантажити його кнопкою Resetабо вимкнути та включити живлення мікроконтролера. Для цього ми маємо перемички. Різні комбінації заганяють мікроконтролер у різні режими. Нас цікавить лише один режим. Для цього у мікроконтролера на висновку BOOT0має бути логічна одиниця, а на висновку BOOT1- Логічний нуль. На платі це таке положення перемичок:

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

Програмне забезпечення для прошивки

Якщо використовуємо USB-UART перехідник, ім'я порту буде приблизно таке /dev/ttyUSB0

Отримати інформацію про чіп

Результат:

Читаємо з чіпа у файл dump.bin

sudo stm32flash -r dump.bin /dev/ttyUSB0

Пишемо в чіп

sudo stm32flash -w dump.bin -v -g 0x0 /dev/ttyUSB0

Результат:

Stm32flash 0.4 http://stm32flash.googlecode.com/ Using Parser: Raw BINARY Interface serial_posix: 57600 8E1 Version: 0x22 Option 1: 0x00 Option 2: 0x00 Device ID: 0x0410 (Medium-den bootloader) - Flash: 128KiB (sector size: 4x1024) - Option RAM: 16b - System RAM: 2KiB Завантажити пам'яту Erasing memory Wrote and verified address 0x08012900 (100.00%) Done. Starting execution at address 0x08000000... done.

Прошивка STM32 за допомогою ST-Link програматора під Windows

При використанні програматора ST-Linkвисновки BOOT0і BOOT1не використовуються і повинні стояти у стандартному положенні для нормальної роботи контролера.

(Книжка російською мовою)

Маркування STM32

Device familyProduct typeDevice subfamilyPin countFlash memory sizePackageTemperature range
STM32 =
ARM-based 32-bit microcontroller
F = General-purpose
L = Ultra-low-power
TS = TouchScreen
W = Wireless system-on-chip
60 = multitouch resistive
103 = performance line
F = 20 pins
G = 28 pins
K = 32 pins
T = 36 pins
H = 40 pins
C = 48/49 pins
R = 64 pins
O = 90 pins
V = 100 pins
Z = 144 pins
I = 176 pins
B = 208 pins
N = 216 pins
4 = 16 Kbytes of Flash memory
6 = 32 Kbytes of Flash memory
8 = 64 Kbytes of Flash memory
B = 128 Kbytes of Flash memory
Z = 192 Kbytes of Flash memory
C = 256 Kbytes of Flash memory
D = 384 Kbytes of Flash memory
E = 512 Kbytes of Flash memory
F = 768 Kbytes of Flash memory
G = 1024 Kbytes of Flash memory
I = 2048 Kbytes of Flash memory
H = UFBGA
N = TFBGA
P = TSSOP
T = LQFP
U = V/UFQFPN
Y = WLCSP
6 = Industrial temperature range –40…+85 °C.
7 = Industrial temperature range, -40…+105 °C.
STM32F103 C8 T6

Як зняти захист від запису/читання?

Якщо ви отримали плату із STM32F103, а програматор її не бачить, це означає, що китайці захистили Флеш пам'ять мікроконтролера. Питання "навіщо?" залишимо поза увагою. Щоб зняти блокування, підключимо UART перехідник, програмуватимемо через нього. Виставляємо перемички для програмування та поїхали:

Я це робитиму з-під Ubuntu за допомогою утиліти stm32flash.

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

Sudo stm32flash /dev/ttyUSB0

Повинні отримати щось таке:

Stm32flash 0.4 http://stm32flash.googlecode.com/ Interface serial_posix: 57600 8E1 Version: 0x22 Option 1: 0x00 Option 2: 0x00 Device ID: 0x0410 (Medium-density) 128KiB (sector size: 4x1024) - Option RAM: 16b - System RAM: 2KiB

2. Знімаємо захист від читання, а потім від запису:

Sudo stm32flash -k /dev/ttyUSB0 stm32flash 0.4 http://stm32flash.googlecode.com/ Interface serial_posix: 57600 8E1 Version: 0x22 1: 0x00 Option 2: 0x00 Device ID: 0 512b reserved by bootloader) - Flash: 128KiB (sector size: 4x1024) - Option RAM: 16b - System RAM: 2KiB Read-UnProtecting flash Done. sudo stm32flash -u /dev/ttyUSB0 stm32flash 0.4 http://stm32flash.googlecode.com/ Interface serial_posix: 57600 8E1 Version: 0x22 1: 0x00 Option 2: 0x00 Device ID: 0 512b reserved by bootloader) - Flash: 128KiB (sector size: 4x1024) - Option RAM: 16b - System RAM: 2KiB Write-unprotecting flash Done.

Тепер можна нормально працювати із мікроконтролером.

© 2023 androidas.ru - Все про Android