- Передмова
- Чому FLASH?
- структура
- Операції з flash-пам'яттю
- Ініціалізація Flash.
- Читання даних з flash-пам'яті.
- Запис даних у flash-пам'ять.
- Використання структур даних.
- Блокування читання / запису Flash
- Програмне виконання.
- Модифікована функція установки / зняття захисту від запису Flash.
- Використання утиліти STM32 ST-LINK Utility.
- Висновок
Перше, з-за чого виникло бажання більш детально розібратися з flash-пам'яттю, стало те, що в своїх проектах хотілося мати незалежну пам'ять, але на жаль серія мікроконтролерів STM32f1xx не має вбудованої eeprom. По-друге, без знання роботи flash було б проблематично написати власний bootloader.
Крім, власне, вбудованої flash-пам'яті, я також трохи розповім і про оперативної пам'яті. Як завжди, основним джерелом для статті є рідне керівництво від компанії STMicroelectronics (Reference Manual), а також узагальнена інформація з різних форумів і статей в інтернеті, ну і на основі свого досвіду.
Передмова
Спочатку планувалося об'єднати в одній статті інформацію про структуру, регістрах і методах роботи з Flash. Але в підсумку, стаття вийшла б занадто громіздкою, тому було прийнято рішення написати дві статті: одна для початківців, використовуючи готові бібліотеки від STM, а друга стаття буде про те, як, власне, ці бібліотеки працюють з пам'яттю.
У даній статті я розповім про структуру пам'яті, основні вимоги при роботі з нею і про деякі моменти, які можуть допомогти швидше розібрати з Flash.
Чому FLASH?
У багатьох невеликих проектах виникає необхідність зберігати будь-які параметри роботи пристрою в незалежній від електрики пам'яті (EEPROM). Але в STMicroelectronics вирішили, що використання цієї пам'яті в більшості мікроконтролерів не доцільно, виняток становить лише серія LP (Low Power), в якій EEPROM присутня.
У великих проектах проблеми немає - використовується зовнішня пам'ять, а ось в невеликих використання зовнішньої пам'яті не завжди вихід, так як, крім усього іншого, потрібно цю пам'ять розмістити на платі і правильно розвести.
В інтернеті повно різних прикладів, є навіть вишукування про те, як зберігати невеликий обсяг даних в регістрах backup'a, але все ж, якщо не вдаватися до зовнішньої пам'яті, основним способом зберігання даних із захистом при відключенні харчування є зберігання у внутрішній flash-пам'яті .
В першу чергу flash-пам'ять призначена для зберігання інструкцій для мікроконтролера, іншими словами - програмного коду. Але якщо Ваш програмний код займає не весь обсяг пам'яті, то чому б не виділити її частина під зберігання наших налаштувань?
Плюси очевидні:
- Не потрібно використання зовнішньої пам'яті, відповідно скорочується час на розробку плати і відбувається здешевлення продукту;
- Менше програмного коду, отже менший час витрачається на розробку.
Є звичайно і свої обмеження:
- Запис у Flash вимагає деякого часу, що впливає на продуктивність МК в момент запису або очищення пам'яті;
- Щоб внести зміни у вже існуючі дані, потрібно стерти всю сторінку або записувати в "чистий" блок пам'яті;
- Кількість циклів перезапису гарантовано в районі 100 тисяч операцій - начебто багато, але перезаписуючи дані в сторінку раз в секунду, МК виробить ресурс flash трохи більше, ніж за добу. Тому дуже не рекомендую постійно писати під flash, рекомендується проводити операції очищення / записи лише для збереження даних в "незалежній" пам'яті. В інших випадках працюємо з оперативною пам'яттю;
- Мінімум Ви можете використовувати 1 сторінку пам'яті (навіть для одного байта), а розмір однієї сторінки складає від одного до двох кілобайт, в залежності від моделі мікроконтролера. Така селяві пристрої flash-пам'яті.
Але не варто забувати що в першу чергу flash-пам'ять призначена для зберігання програмного коду і, відповідно, сама структура інтерфейсу Flash в мікроконтролері влаштована таким чином, щоб оптимізувати завантаження інструкцій для МК і організувати їх (інструкцій) кешування, поки йде виконання вже завантажених.
структура
Як нам повідомляє Reference Manual, модуль flash-пам'яті є високопродуктивним і має наступні ключові особливості:
- Для пристроїв серії XL-density: обсяг пам'яті становить до 1Mb, розбитий на два банки пам'яті:
- 1-й банк пам'яті: 512KB;
- 2-й банк пам'яті: до 512KB, в залежності від моделі мікроконтролера.
- Для інших пристроїв: обсяг пам'яті становить до 512KB, що складається з одного банку пам'яті розміром до 512KB.
- Сама по собі Flash-пам'ять складається з трьох блоків: основний блок пам'яті, інформаційний блок і блоку регістрів управління Flash. Структура блоків пам'яті і їх характеристики наведені на малюнку №1.
Інтерфейс flash-пам'яті (FLITF (Flash Memory Interface)) дозволяє виконувати наступні операції:
- Читання з пам'яті з можливістю буферизації (2 слова по 64 біта);
- Операції запису і очищення пам'яті;
- Організація захисту flash від читання і / або запису.
Основна flash-пам'ять розбита на сторінки розміром в 1 або 2 кілобайт, це залежить від лінійки мікроконтролерів STM. Використовувати під свої потреби Ви можете тільки цілу кількість сторінок, відповідно, якщо Ваша прошивка займає все сторінки пам'яті МК, то використання flash-пам'яті під свої потреби буде проблематично.
Два залишилися блоку пам'яті, інформаційний та блок регістрів, досить рідко використовуються безпосередньо, в основному використовується функціонал з готових бібліотек. Тому в подальшому описі я торкнуся лише регістра настройки, а докладний опис регістрів пам'яті та інформаційного блоку я приведу в окремій статті.
Операції з flash-пам'яттю
Операцій роботи з flash-пам'яттю не так вже й багато: можемо прочитати, записати і очистити пам'ять. Причому запис і очищення пам'яті, як правило, здійснюється разом. Також ще можна заблокувати пам'ять для читання і / або для запису. Але перед початком роботи необхідно провести ініціалізацію модуля Flash.
Ініціалізація Flash.
Операції читання flash-пам'яті і доступ до даних виконуються через шину AHB. Буферизація читання, для виконання команд, виконується через шину ICode. Арбітраж виконання здійснюється самою flash-пам'яттю, а пріоритет віддається доступу до даних на шині DCode. Іншими словами, поки мікроконтролер виконує поточну команду, інтерфейс Flash-пам'яті передає МК дані за наступною.
Читання даних з flash-пам'яті може бути налаштоване таким чином:
• Затримка (Latency): кількість станів очікування для операції читання, встановлюється «на льоту», не потрібно ініціалізація МК;
• Буферизація (2 слова по 64 біта) (Prefetch buffer): активується під час ініціалізації МК. Весь блок пам'яті може бути замінений за один цикл читання з пам'яті, так як розмір блоку відповідає розміру смуги пропускання шини flash-пам'яті. Завдяки буферизації, можлива більш швидка робота МК, оскільки МК витягує з пам'яті по одному "слову" за раз одночасно з наступним "словом", яке міститься в попередній буфер.
• Половинний цикл (Half cycle): цей режим потрібно для оптимізації харчування, найчастіше використовується при роботі від батареї з програмами, які не вимагають особливого швидкодії.
Ці параметри слід використовувати відповідно до пори доступу до flash-пам'яті. Значення періоду очікування являє собою відношення періоду SYSCLK (системних годин) до часу доступу до flash-пам'яті і встановлюється в залежності від наступних параметрів:
- Рівень "0", якщо 0 <SYSCLK ≤ 24 MHz;
- Рівень "1", якщо 24 MHz <SYSCLK ≤ 48 MHz;
- Рівень "2", якщо 48 MHz <SYSCLK ≤ 72 MHz;
Конфігурація з половинним циклом недоступна в поєднанні з предделителя на AHB. Системний годинник (SYSCLK) повинні бути рівні годинах HCLK. Тому цю функцію можна використовувати тільки з низькочастотними тактовою частотою 8MHz-менш. Він може бути створений з HSI або HSE, але не з PLL.
Буфер попередньої вибірки повинен зберігатися при використанні предільника, відмінного від 1 на годиннику AHB.
Буфер попередньої вибірки повинен бути включений / виключений тільки тоді, коли SYSCLK нижче 24MHz і на годиннику AHB не застосовується попередній дільник (SYSCLK має дорівнювати HCLK). Буфер попередньої вибірки зазвичай включається / вимикається під час процедури ініціалізації, а мікроконтролер працює на вбудованому генераторі 8MHz (HSI).
Це інформація досить складна для сприйняття при початковому рівні знань, для розуміння її роботи потрібне вміння налаштовувати тактирование МК і всіх його інтерфейсів.
Для ініціалізації Flash призначений регістр FLASH_ACR, який використовується для включення / вимикання буферизації і повного циклу читання, а також для управління затримкою часу доступу до flash-пам'яті в залежності від настроєної частоти роботи процесора. У наведеній нижче таблиці наведені бітові карти і опису біт для цього регістра.
Мал. 2. Flash Access Control register Address offset: 0x00
Reset value: 0x0000 0030
0: відключена; 1: включена. Біт доступний тільки для читання. 4 PRFTBE - Prefetch buffer enable Включення буферизації.
0: відключена; 1: включена. 3 HLFCYA - Flash half cycle access enable Доступ до повного циклу Flash.
0: відключений; 1: включений. 2: 0 LATENCY - Latency Управління затримкою.
Ці біти представляють відношення періоду SYSCLK (системних годин) до часу доступу Flash.
000 Рівень "0", якщо 0 <SYSCLK≤ 24 МГц
001 Рівень "1", якщо 24 МГц <SYSCLK ≤ 48 МГц
010 Рівень "2", якщо 48 МГц <SYSCLK ≤ 72 МГц Таб. 1. Опис регістра FLASH_ACR (Flash access control register)
Тепер подивимося, як це виглядає в програмному коді:
Лістинг №1. Процедура настройки flash-пам'яті void FLASH_Init (void) {FLASH_HalfCycleAccessCmd (FLASH_HalfCycleAccess_Disable); // Відключаємо половинний цикл доступу до пам'яті, використовуємо повнийFLASH_PrefetchBufferCmd (FLASH_PrefetchBuffer_Enable); // Включаємо кешування FLASH_SetLatency (FLASH_Latency_2); // Налаштовуємо затримку з урахуванням частоти роботи МК}
Налаштування затримки необхідно виконувати відповідно до настройками Вашого мікроконтролера, тут лише наведені приклади команд.
Саму процедуру ініціалізації рекомендується виконувати або перед ініціалізацією мікроконтролера, або безпосередньо з функції ініціалізації МК (за винятком затримки, її можна налаштовувати в будь-який момент часу роботи програми).
Читання даних з flash-пам'яті.
Читання flash не представляє ніяких складнощів для розуміння, достатньо звернутися до необхідної комірки пам'яті:
Лістинг №2. Читання даних з flash-пам'яті value = * (__ IO uint32_t *) address;Ось і все, для читання нам більше нічого не потрібно! Єдиною про що потрібно пам'ятати, це те, що читання відбувається блоками по 32 біта.
Щоб зробити код більш наочним, читання з flash-пам'яті я виніс в окрему функцію
Лістинг №3. Функція читання даних з flash-пам'яті uint32_t flash_read (uint32_t address) {return (* (__ IO uint32_t *) address); }Запис даних у flash-пам'ять.
Запис даних у Flash влаштована трохи складніше, ніж читання. Основною відмінністю Flash від оперативної пам'яті є те, що перед тим, як зробити запис значення в клітинку, її необхідно очистити. Не можна ось просто так записати в пам'ять спочатку одне значення, потім інше - так не вийде. Але і очистити комірку пам'яті просто так не можна! Можна очистити тільки сторінку цілком або відразу всю пам'ять МК.
Операція стирання пам'яті не обнуляє, як здавалося б логічним, а встановлює значення бітів пам'яті рівним одиниці. А операція записи тільки обнуляє необхідні біти. Перезаписати нуль в одиницю не можна, можна тільки стерти всю сторінку пам'яті цілком. Це основна незручність використання Flash. Тому використовуються два способу перезапису даних користувача: перший - послідовно, коли параметри сторінки не стираються, а дописують в кінець даних, поки не закінчиться сторінка; і другий спосіб, коли перед зміною даних стирається вся сторінка цілком, а потім вже все параметри записуються заново. При першому випадку пам'ять прослужить довше, а другий більш зручний для використання.
Причина цього в тому, що Flash пам'ять не є оперативною, вона набагато більш повільна, ніж оперативна, і призначена для зберігання набору інструкцій для мікроконтролера. Але є і великий плюс: при відключенні харчування вона не стирається.
Зазвичай під свої потреби програмісти використовують одну-дві останніх сторінки пам'яті МК. Це обумовлено тим, що код програми може модифікуватися і займати все більше обсягу flash. А так як, запис інструкцій для МК проводиться, як правило, з першої сторінки, то може статися ситуація, що запис нової прошивки просто перетремо призначені для користувача дані. Виняток становлять ситуації, коли, наприклад, в мікроконтролер "заливається" автозавантажувач (bootloader) і основна програма починається не з першої сторінки, а, наприклад, з десятої. Але це тема окремої статті і тут згадка буде тільки побіжно.
А тепер, коли ми ознайомилися з теорією, розберемо вже практичні способи реалізації очищення і перезапису flash пам'яті.
Лістинг №4. Процедура запису даних у flash-пам'ять #define FIRMWARE_PAGE_OFFSET 0x0C00 void WriteToFlash (uint32_t Value) {uint8_t i; uint32_t pageAdr; pageAdr = NVIC_VectTab_FLASH | FIRMWARE_PAGE_OFFSET; // Адреса сторінки пам'яті FLASH_Unlock (); // розблокуємо пам'ять для запису FLASH_ErasePage (pageAdr); // Очистимо сторінку пам'яті FLASH_ProgramWord ((uint32_t) (pageAdr), (uint32_t) Value); // Запишемо нове значення пам'яті FLASH_Lock (); }Як Ви напевно помітили, запис в пам'ять відбувається між двома командами "FLASH_Unlock ()" і "FLASH_Lock ()". Неважко здогадатися, що це пов'язано з розблокуванням пам'яті для можливості проводити запис у Flash. Відповідно, після запису, Flash потрібно закрити для зміни.
NVIC_VectTab_FLASH і PARAMS_PAGE_OFFSET - це визначення адрес пам'яті і зміщення, щодо початкової адреси.
Щоб було зрозуміліше - розберемо приклад:
Розмір прошивки Автозавантажувач становить менш 3Kb, відповідно нам потрібно почати запис основний прошивки в третю сторінку або вище з урахуванням того, щоб прошивка вмістилася в пам'ять, що залишилася. Не забувайте, що сторінки flash-пам'яті починаються з нуля, відповідно четверта за рахунком сторінка називається "Page3".
NVIC_VectTab_FLASH - зазвичай дорівнює 0x08000000, він визначений в системі (в файлі misc.h).
FIRMWARE_PAGE_OFFSET- це наше визначення і формується за такою формулою: НомерСтраніциПамяті * РазмерСтраніциПамяті. Номер сторінки - ми визначаємо самостійно, розмір сторінки пам'яті для більшості МК серії STM32F103 становить 1Kb, за винятком мікроконтролерів лінійки HD і CL (Connectivity Line), в яких вона дорівнює двом кілобайтам.
У нашому випадку виходить: (4-1) * 0x0400 = 0x0C00 - це зміщення щодо початкової адреси. Відповідно писати дані ми будемо починати з адреси 0x08000C00.
Можна звичайно і відразу вказати адресу, куди будемо писати (0x08000C00), але краще відразу звикати писати "правильно" і з ухилом на майбутнє, щоб Ваш код без особливої переробки міг мігрувати на інші МК.
Щоб стерти сторінку пам'яті, досить в команду FLASH_ErasePage як параметр передати будь-яку адресу з діапазону адрес сторінки. Як правило передають адресу першого блоку потрібної сторінки. Не помиліться з визначенням сторінки пам'яті, так як можна стерти і саму прошивку))).
Запис проводиться блоками по чотири байти - це називається "словом" розміром в 32 біта. Якщо спробувати зробити запис за адресою, який не кратний чотирьом, то програма піде в нескінченний цикл очікування закінчення запису.
Також варто звернути увагу на те, що перед записом Вам необхідно очистити стільки сторінок, скільки кілобайт пам'яті Вам необхідно записати округляючи до більшого цілого (за умови що одна сторінка = 1Kb).
Ну і після закінчення запису, необхідно заблокувати Flash для подальшої зміни.
Використання структур даних.
Дуже зручно зберігати дані у вигляді структури, але існують деякі тонкощі, які можуть зіпсувати чимало нервових клітин не тільки у початківців розробників, але і у бувалих.
Саме визначення структури виглядає приблизно так:
Лістинг №5. Визначення структури даних параметрів // Визначимо структуру typedef struct {uint8_t Param1; // 1 byte uint8_t Param2; // 1 byte uint16_t Param3; // 2 byte uint8_t Param4; // 1 byte uint8_t Param5; // 1 byte uint16_t Param6; // 2 byte uint16_t Param7; // 2 byte uint16_t Param8; // 2 byte uint32_t Param9; // 4 byte} Params_def; // Тепер змінна static Params_def params; // опеределить, скільки блоків пам'яті у нас визначено під параметри // Це необхідно для визначення кількості циклів читання / запису параметрів #define PARAMS_WORD_CNT sizeof (params) / sizeof (uint32_t) // Розраховується виходячи з розміру структури в пам'яті МК поділеного на розмір блоку (4 байта)Як бачите - нічого складного немає.
Коли Ви визначаєте змінну, під неї резервується місце в оперативній пам'яті в розмірі, який Ви визначили. Адреси пам'яті резервуються поспіль, в блоках по чотири байти. Відповідно, якщо ви визначаєте поспіль три змінних по байту, а потім одну, розміром в два байта, то в пам'яті вони займуть в цілому 8 байт: 3 байта в першому блоці і два в другому. В силу специфіки роботи з оперативною пам'яттю, займати пам'ять частинами не вийде!
На малюнку №3 я відобразив, як буде розподілятися пам'ять при визначенні змінних.
Мал. 3. Розміщення змінних в пам'ятіЯк видно з малюнка, для економії пам'яті і правильної її організації, змінні необхідно оголошувати таким чином, щоб обсяг займаної ім'я пам'яті був кратним чотирьом. Це ж правило стосується і визначення структури пам'яті.
В цілому, помилки в розміщення в неправильному порядку змінних немає, при сучасних обсягах оперативки це не критично. Але правила хорошого тону в програмуванні настійно рекомендують визначати змінні з урахуванням їх розміщення в пам'яті. Перевірити себе, можна через відладчик, подивившись зріз пам'яті і порядок заповнення оперативної пам'яті МК за адресою 0x20000000.
В даному прикладі, правильний порядок визначення змінних цю вимогу, а не рекомендація, так як нам потрібно знати точну кількість блоків пам'яті, в яких розміститься змінна з нашим визначенням структури. Другий приклад з помилкою на малюнку 3 показує, що якщо ми не так визначимо порядок змінних, то обсяг займаної пам'яті в структурі буде займати не один блок, як ми думали, а два. Відповідно при збереженні даних в Flash, ми можемо втратити частину інформації.
Тепер я наведу приклади читання і запису параметрів з / в Flash одним блоком.
Змінна з нашої структурою зберігається в оперативній пам'яті, при її визначенні в оперативній пам'яті виділяється певний об'єм байт. Ми можемо записувати у Flash не тільки кожен параметр окремо, а цілком всю структуру, для цього ми визначаємо адресу осередки оперативної пам'яті, з якого починається наша структура, і цілком копіюємо його в flash. Читання даних відбувається в зворотному порядку.
Ну а тепер приклади:
Лістинг №6. Запис структури даних у Flash void FLASH_WriteSettings (void) {uint8_t i; uint32_t pageAdr; // Ці параметри можуть заповнюватися в будь-якому місці програми params.Counter1 = 0xc1; params.Counter2 = 0xc2; params.Counter3 = 0xc3; params.Counter4 = 0xc4; params.Nothing1 = 0xF1F1; params.Nothing2 = 0xF2F2; pageAdr = NVIC_VectTab_FLASH + PARAMS_PAGE_OFFSET; // Адреса сторінки пам'яті uint32_t * source_adr = (void *) & params; FLASH_Unlock (); // розблокуємо пам'ять для запису FLASH_ErasePage (pageAdr); // Очистимо сторінку пам'яті параметрів №0 for (i = 0; i <PARAMS_WORD_CNT; ++ i) {FLASH_ProgramWord ((uint32_t) (pageAdr + i * 4), * (source_adr + i)); // Запишемо нове значення пам'яті} FLASH_Lock (); // Заблокуємо Flash}Хочеться звернути Вашу увагу на рядок, в якій відбувається запис у flash-пам'ять:
FLASH_ProgramWord ((uint32_t) (pageAdr + i * 4), * (source_adr + i));В одному випадку адреса збільшується на "i * 4", а в іншому просто на "i" - Це не помилка: в першому випадку у нас змінна визначена як "uint32_t", а в іншому як посилання на елемент пам'яті розміром "uint32_t". Відповідно в першому випадку ми змінюємо число на чотири байти, а в другому - змінюємо на одиницю посилання на комірку пам'яті розмірів в 4 байта.
Тепер подивимося на те, як виглядає заповнення нашої структури даних з Flash:
Лістинг №7. Читання структури даних з Flash void Flash_ReadParams (void) {uint32_t * source_adr = (uint32_t *) (NVIC_VectTab_FLASH + PARAMS_PAGE_OFFSET); // Визначаємо адреса, звідки будемо читати uint32_t * dest_adr = (void *) & params; // Визначаємо адреса, куди будемо писати for (uint16_t i = 0; i <PARAMS_WORD_CNT; ++ i) {// В циклі виробляємо читання * (dest_adr + i) = * (__ IO uint32_t *) (source_adr + i); // Саме читання}}Тут все теж саме, що і в попередньому лістингу, з тією лише різницею, що ми читаємо записані значення в дані нашої структури.
Блокування читання / запису Flash
Підійшов час ознайомиться з захистом даних від читання і / або запису.
У якийсь момент часу у розробників настає момент, коли хочеться заховати якийсь код або дані від сторонніх очей або захистити від зміни свою прошивку. Виконати це можна декількома способами, розглянемо два з них: програмно або через стандартну утиліту "STM32 ST-LINK Utility".
Відразу обмовлюся, захист від читання, це не панацея - в мережі повно сайтів, які пропонують прочитати прошивку не пошкодивши дані. Яким чином, невідомим мені (поки)))), вони знімають біт захисту від читання і блокують стирання flash. Тому в "крутих" прошивках, захищених від читання, присутній ще і додатковий захист, що дозволяє уникнути клонування і декомпіляцію коду.
Установка захисту читання і запису даних встановлюється за допомогою "Options Byte", що знаходяться в інформаційному блоці Flash. У цій статті докладно розписувати роботу не буду, зупинюся лише на ключових моментах.
Захист від читання реалізована майже геніально: встановлюється біт заборони читання flash (RDP - Read Protection). Якщо біт знімається, то весь вміст Flash стирається. Встановити біт і зняти його можна як програмно, так і за допомогою сторонніх утиліт.
Захист від запису реалізовано посторінково, блоками по 4 кілобайти: для мікроконтролерів з розміром сторінки рівним одному кілобайт, блокується відразу по чотири сторінки пам'яті, для МК з розміром пам'яті рівним двом кілобайт - блокується по дві сторінки. Захист здійснюється в осередках пам'яті "Options Byte" за допомогою зміни бітів WRPn (Write Protection).
Програмне виконання.
Найпростіший спосіб захистити прошивку від читання, це скористатися стандартною бібліотекою від STM. Код буде виглядати приблизно так:
Лістинг №8. Захист Flash від читання #ifndef DEBUGMODE // DEBUGMODE - Наше визначення, щоб можна було тестувати прошивку if (FLASH_GetReadOutProtectionStatus () == RESET) // Перевіряємо, чи встановлена захист {// Якщо немає: FLASH_Unlock (); // розблокуємо пам'ять Flash FLASH_ReadOutProtection (ENABLE); // Встановлюємо захист від читання FLASH_Lock (); // Блокуємо пам'ять Flash} #endifЯк бачите, нічого складного немає.
Дефайн "DEBUGMODE" визначаємо в тому випадку, якщо нам треба налагодити прошивку, інакше, при включеному захисті від читання, пройтися отладчиком по прошивці не вийде.
Якщо встановлений захист від читання, але не встановлений захист від запису, то ми не можемо тільки прочитати прошивку, а залити нову - без проблем.
До речі деякий спостереження, захист від читання можна не тільки встановити, але і зняти через програмний код. Навіщо це потрібно - вирішувати Вам))).
З читанням все зрозуміло, для чого воно потрібне - теж, а ось навіщо потрібен захист від запису? Перше, напевно, що приходить в голову, це наш автозавантажувач, щоб випадково при прошивці мікроконтролера, ми не затерли свій Bootloader.
Ось як виглядає процедура установки захисту від запису на перші 8 кілобайт пам'яті:
Лістинг №9. Функція захисту від записи #ifndef DEBUGMODE // DEBUGMODE - Наше визначення, щоб можна було тестувати прошивку і перепрошивати її void Lock_Bootloader (void) {unsigned int WRPR_Value; // Список закритих для запису сторінок unsigned int ProtectedPages; // Список сторінок, які необхідно закрити FLASH_Unlock (); // розблокуємо пам'ять Flash WRPR_Value = FLASH_GetWriteProtectionOptionByte (); // Отримаємо список вже заблокованих сторінок ProtectedPages = WRPR_Value & (FLASH_WRProt_Pages0to3 | FLASH_WRProt_Pages4to7); // Встановимо тільки ті сторінки, які нам необхідно захистити if (ProtectedPages! = 0x00) // Якщо є що захищати, то {FLASH_EraseOptionBytes (); // Очистимо Option Byte (Це необхідно зробити до установки записи) FLASH_EnableWriteProtection (ProtectedPages); // Встановимо захист пам'яті від запису} FLASH_Lock (); // Блокуємо пам'ять Flash} #endifДані про захищених сторінках пам'яті зберігаються в бітах Operation Byte. Захищеними вважаються ті сторінки, біт яких скинутий. Якщо біт встановлено, то сторінка доступна для запису! Принцип роботи з Option Byte такий же, як і з усією Flash - перед установкою значення, його необхідно очистити, при цьому всі біти встановлюються в 1.
Хочу звернути Вашу увагу на перевірку умови "if (ProtectedPages! = 0x00)" - умова вірне, ми порівнюємо ProtectedPages, а не WRPR_Value. У ProtectedPages встановлені біти тільки тих блоків пам'яті, які нам потрібні і вони не захищені від запису. А в WRPR_Value встановлені біти захищених блоків пам'яті.
Також, необхідно перевірити, раптом вже встановлено те, що нам потрібно і тоді не будемо "мучити" Flash і перезаписувати дані.
До речі, не намагайтеся змінювати значення Option Byte "на льоту". Читання цього блоку відбувається при перезавантаженні мікроконтролера і зберігається в регістрах FLASH_OBR і FLASH_WRPR, тому змінювати значення в процесі виконання коду без перезавантаження МК безглуздо - збережено буде останнім встановлене Вами значення.
Модифікована функція установки / зняття захисту від запису Flash.
На прохання Serg, привожу приклад функції установки або скидання захисту від запису. Чомусь розробники від STM посоромилися додати цю функцію в бібліотеку для лінійки МК STM32F1xx, хоча для інших лінійок МК такий (схожий) функціонал присутній. Основна її відмінність від рідної функції в тому, що перед записом в регістр Operation Byte необхідно проводити його очищення, відповідно при використанні рідної функції відбувається скидання всіх настройок безпеки, а в моїй функції реалізовано зміна окремих біт, що дозволяє більш гнучко управляти захистом, хоча і згубно впливає на МК, так як кількість циклів перезапису для Flash гарантовано в межах 100000 циклів. (Навіть на тестових МК я ще жодного разу не зіткнувся з тим, що у мене кількість циклів перезапису перевалило за сотню тисяч)))).
Саме тіло функції бажано розмістити в модулі "stm32f10x_flash.c" після функції "FLASH_EnableWriteProtection ()":
Лістинг №10-а. Функція установки / зняття захисту від запису / ** * @brief Включення / відключення захисту від запису Flash пам'яті * @note Перероблена і доповнена процедура установки захисту від запису (оригінал FLASH_EnableWriteProtection) * @note Ця функція може бути використана на всіх пристроях серії STM32F10x. * @Param FLASH_Pages: перелік блоків сторінок пам'яті, для яких необхідно встановити нове значення. * This parameter can be: * @arg For @b STM32_Low-density_devices: value between FLASH_WRProt_Pages0to3 and FLASH_WRProt_Pages28to31 * @arg For @b STM32_Medium-density_devices: value between FLASH_WRProt_Pages0to3 * and FLASH_WRProt_Pages124to127 * @arg For @b STM32_High-density_devices: value between FLASH_WRProt_Pages0to1 and * FLASH_WRProt_Pages60to61 or FLASH_WRProt_Pages62to255 * @arg For @b STM32_Connectivity_line_devices: value between FLASH_WRProt_Pages0to1 and * FLASH_WRProt_Pages60to61 or FLASH_WRProt_Pages62to127 * @arg For @b STM32_XL-density_devices: value between FLASH_WRProt_Pages0to1 and * FLASH_WRProt_Pages60to61 or FLASH_WRProt_Pages62to511 * @arg FLASH_WRProt_AllPages * @param NewState: новий стан , яке необхідно встановити для зазначених блоків пам'яті. Дайте відповідь на це параметр може приймати значення: * @arg ENABLE - установка блокування записи * @arg DISABLE - вимикання блокування записи * @retval FLASH Status: Значення, що повертається може бути: FLASH_ERROR_PG, * FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT. * / FLASH_Status FLASH_SetWriteProtection (uint32_t FLASH_Pages, FunctionalState NewState) {uint16_t WRP0_Data = 0xFFFF, WRP1_Data = 0xFFFF, WRP2_Data = 0xFFFF, WRP3_Data = 0xFFFF; uint16_t oldData0, oldData1, oldData2, oldData3; uint16_t newData0, newData1, newData2, newData3; FLASH_Status status = FLASH_COMPLETE; / * Check the parameters * / assert_param (IS_FLASH_WRPROT_PAGE (FLASH_Pages)); FLASH_Pages = (uint32_t) (~ FLASH_Pages); WRP0_Data = (uint16_t) (FLASH_Pages & WRP0_Mask); WRP1_Data = (uint16_t) ((FLASH_Pages & WRP1_Mask) >> 8); WRP2_Data = (uint16_t) ((FLASH_Pages & WRP2_Mask) >> 16); WRP3_Data = (uint16_t) ((FLASH_Pages & WRP3_Mask) >> 24); / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); if (status == FLASH_COMPLETE) {// Скопіюємо поточні настройки захисту, тому що ми потім їх зітремо oldData0 = OB-> WRP0 & 0xFF; oldData1 = OB-> WRP1 & 0xFF; oldData2 = OB-> WRP2 & 0xFF; oldData3 = OB-> WRP3 & 0xFF; // Очистимо Option Byte (Це необхідно зробити до установки записи, інакше нічого не зможемо змінити) FLASH_EraseOptionBytes (); / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); / * Authorizes the small information block programming * / FLASH-> OPTKEYR = FLASH_KEY1; FLASH-> OPTKEYR = FLASH_KEY2; FLASH-> CR | = CR_OPTPG_Set; // Розрахуємо нові значення if (NewState == ENABLE) {newData0 = oldData0 & WRP0_Data; newData1 = oldData1 & WRP1_Data; newData2 = oldData2 & WRP2_Data; newData3 = oldData3 & WRP3_Data; } Else {newData0 = ~ (~ oldData0 & WRP0_Data); newData1 = ~ (~ oldData1 & WRP1_Data); newData2 = ~ (~ oldData2 & WRP2_Data); newData3 = ~ (~ oldData3 & WRP3_Data); } If (status == FLASH_COMPLETE) {OB-> WRP0 = newData0; / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); } If (status == FLASH_COMPLETE) {OB-> WRP1 = newData1; / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); } If (status == FLASH_COMPLETE) {OB-> WRP2 = newData2; / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); } If (status == FLASH_COMPLETE) {OB-> WRP3 = newData3; / * Wait for last operation to be completed * / status = FLASH_WaitForLastOperation (ProgramTimeout); } If (status! = FLASH_TIMEOUT) {/ * if the program operation is completed, disable the OPTPG Bit * / FLASH-> CR & = CR_OPTPG_Reset; }} / * Return the write protection operation Status * / return status; }І необхідно додати визначення функції в модулі "stm32f10x_flash.h" також після функції "FLASH_EnableWriteProtection ()", інакше Ви не зможете викликати її з звий програми:
Лістинг №10-b. Заголовок функції установки / зняття захисту від записи FLASH_Status FLASH_SetWriteProtection (uint32_t FLASH_Pages, FunctionalState NewState);Сама функція є переробленою функцією від STM "FLASH_EnableWriteProtection ()":
- доданий параметр "NewState", який відповідає за установку або зняття прапора захисту;
- в тілі функції додана перевірка на значення цього параметра і, в залежності від нього, встановлюються або скидаються відповідні біти.
- скидання OperationByte проводиться не перед виклик функції, а всередині неї. Це пов'язано з особливістю записи в Flash, тому спочатку запам'ятовуються попередні значення OperationByte, проводиться очищення OperationByte, а потім вже записуються нові значення з урахуванням попередніх.
Таким чином можна замінити за своїм кодом виклики "FLASH_EnableWriteProtection (ProtectedPages)" на "FLASH_SetWriteProtection (ProtectedPages, ENABLE)" для включення захисту, або "FLASH_SetWriteProtection (ProtectedPages, DISABLE)" для її відключення. Не забудьте в цьому випадку прибрати очищення OperationByte за допомогою функції «FLASH_EraseOptionBytes ()", так як вона скине все раннє встановлені прапори захисту від запису.
Використання утиліти STM32 ST-LINK Utility.
Ви можете захистити свою прошивку від читання і запису за допомогою стандартної утиліти від STM. Завантажити її можна на Офіційному сайті , Пройшовши нескладну реєстрацію.
З усіх можливостей утиліти, ми звернемося тільки до роботи з областю даних мікроконтролера, відомої як "Option Byte".
Потрапити в цей функціонал програми можна або комбінацією кнопок "Ctrl + B", або через меню "Target" / "Option Byte". Перед Вами відкриється схоже вікно:
Мал. 4. Налаштування Option Byte (STM32 ST-LINK Utility).Нас зараз цікавлять всього два розділи: читання і запис. Тут Ви можете включити або вимкнути захист від читання і галочками встановити сторінки, які хочете захистити від запису. Дії аналогічні описаним вище, тому описувати детально не буду.
Не знаю чому, але якщо Ви встановили захист від запису, то після зняття цієї захисту через утиліту, автоматично виставляється прапор захисту від читання. а зняття прапора захисту від читання повністю стирає Flash.
Висновок
У цій статті я постарався коротко розповісти як можна використовувати Flash в своїх цілях, як зробити свій автозавантажувач (Bootloader) і як захистити своій код. Всі приклади в статті протестовані і готові до використання. На цей раз вкладення додавати не буду - весь матеріал містить невеликий обсяг коду і легко сприймається в тексті.
Якщо знайдете помилки або будуть питання - welcome в коментарі))).
Чому FLASH?Але якщо Ваш програмний код займає не весь обсяг пам'яті, то чому б не виділити її частина під зберігання наших налаштувань?
З читанням все зрозуміло, для чого воно потрібне - теж, а ось навіщо потрібен захист від запису?