======Создание FLM======
FLM - это проект для Keil, который среда загружает в МК по интерфейсу Jtag или SWD. Файл FLM должен реализовать несколько функций, которые будут вызываться при прошивке и которым будут передаваться данные для записи в память. FLM может быть различное множество - для внутренней Flash, для записи во внешнюю память ОЗУ/ПЗУ по внешней шине или SPI, и т.д. То есть это просто проект, который принимает данные извне и знает что и как с ними делать, предварительно настроив необходимую периферию.
В отличие от официальных версий, FLM из текущего проекта будут поддерживать стирание памяти по секторам. Что позволит использовать их совместно с утилитой J-Flash - [[prog:start:J-FLASH|"Программирование контроллеров с помощью J-FLASH Lite"]].
Текущая реализация создает следующие FLM:
* 1986VE1_FlashInt.FLM - для 1986ВЕ1Т и 1986ВЕ3Т, Cortex-M1.
* 1986VE9x_FlashInt.FLM - для 1986ВЕ9х, Cortex-M3.
Для сборки FLM потребуется лицензионная версия Keil. Демо-версия на 32Кб не умеет собирать проект FLM, эта возможность отключена.
Целиком проект доступен на [[https://github.com/StartMilandr/FLM|GitHub]], далее шаги по его созданию на примере версии Keil 5.23:
=====Директории проекта=====
Я завел себе директорию FLM, в которую буду собирать все необходимое для сборки. В этой директории такие папки:
* src - для файлов описания микроконтроллеров и общего для всех МК кода.
* MDR32Fx.h
* MDR1901VC1T.h
* MDR1986BE4.h
* MDR1986VE1T.h
* MDR1986VE3.h
* MDR32F9Qx_rst_clk.h
* MDR32F9Qx_eeprom.h
* MDR32F9Qx_eeprom.c
* test - для тестовых проектов проверки работоспособности функций FLM.
* FLM_Test_VE92
* FLM_Build - для мультипроекта по сборке самих FLM.
* VE9x_FLM_FlashInt
===Директория src===
В директорию src из исходников SPL копируем описанные выше файлы. Пак с SPL обычно устанавливается в C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Libraries.
Файлы описания МК (//MDR1986VEхх.h//, ...) потребуются для использования структур и адресов конкретного МК. Заголовочный файл //MDR32F9Qx_rst_clk.h// необходим для использования определений битов и констант для задания тактирования от HSI. Полагаться на внешние источники нельзя, поскольку неизвестно, чем тактируется МК в изделии (резонатор, генератор и при каких параметрах). Из файлов //MDR32F9Qx_eeprom.c// и //MDR32F9Qx_eeprom.h// мы используем штатные, готовые функции работы с внутренней памятью Flash через контроллер EEPROM. В указанных файлах я закомментировал все, что связывало эти файлы с другими источниками.
В директорию src добавлен файл //flmClock.c//, в него я вытащил реализацию перехода на частоту HSI из библиотечного //MDR32F9Qx_rst_clk.с//.
===Директория test===
Функции, которые необходимо реализовать в FLM файле описаны [[http://arm-software.github.io/CMSIS_5/Pack/html/algorithmFunc.html|здесь]] //(про функции подробнее дальше)//. Для того, чтобы отладить их реализацию, я создаю отдельный тестовый проект для каждого МК, реализую указанные в ссылке варианты вызовов функций, проверяю на МК вживую и отлаживаю. Эти же реализации функций затем используются в проектах, которые собирают сами FLM. При таком подходе, получая FLM, я уверен, что функции с памятью работают корректно.
Для примера рассмотрим создание FLM для МК 1986ВЕ92У. Поэтому в директории test я создаю поддиректорию FLM_Test_VE92 - здесь будет проект для тестирования функций FLM. //(Раскладка памяти и периферии в серии 1986ВЕ9х одинакова, поэтому директорию можно было бы назвать FLM_Test_VE9х. Ведь какой-либо привязки конкретно к 1986ВЕ92 проект не имеет. Просто для отладки использовалась плата для 1986ВЕ92У.)//
===Директория FLM_Build===
В директории FLM_Build в одном workspace будут собраны все проекты, собирающие FLM. Создание FLM рассмотрим на примере проекта для микроконтроллеров 1986ВЕ9х, поэтому создаем директорию VE9x_FLM_FlashInt. В нее сейчас и будем создавать проект.
=====Создаем проект FLM=====
Шаблон проекта для создания FLM находится в директории с готовыми алгоритмами - C:\Keil_v5\ARM\Flash. Из этой директории нам понадобятся:
* Содержимое директории //_Template// - только файлы, без поддиректории Test.
* Файл //FlashOS.h//
//FlashOS.h// копируем в //src// - этот файл общий для всех и необходим Keil при сборке. \\
Содержимое //_Template// копируем в //FLM_Build\VE9x_FLM_FlashInt//. Копируемые проектные файлы имеют название NewDevice, предлагаю обозвать их как-то более осмысленно - меняем названия на VE9x_FLM_FlashInt, расширения файлов сохраняем.
Теперь запускаем переименованный проект //VE9x_FLM_FlashInt.uvproj// и можно заняться кодированием. При открытии проекта, Keil предложит установить [[http://www2.keil.com/mdk5/legacy|"Legacy Support for ARM7, ARM9 & Cortex-R"]]. Этого не требуется, нажимаем Cancel. У меня уже установлен [[http://www2.keil.com/mdk5/legacy|Legacy support for ARM Cortex-M devices]], как рекомендовалось в статье - [[prog:start:setupkeil|Установка Keil и SPL Milandr]]. Если поддержка Cortex-M не установлена, то необходимо ее установить.
{{prog:start:keil_legacyflm.png}}
В проекте используется всего два исходных файла - //FlashDev.c// и //FlashPrg.c//. Дополнительно к этим файлам необходимо подключить исходники из директории scr:
- MDR32F9Qx_eeprom.c
- flmClock.c
В закладке настроек проекта "С++" указываем путь к папке src (поле Include Paths: ..\..\src). В настройках проекта обязательно необходимо выбрать ядро процессора - закладка Device:
* ARM - Cortex-M3 для проекта VE9х_FLM_FlashInt
* ARM - Cortex-M1 для проекта VE1_FLM_FlashInt
====Раскладка Flash памяти - FlashDev.c====
Файл //FlashDev.c// очень короткий и всего-навсего описывает раскладку Flash памяти и название алгоритма. Изначально файл уже содержит значения по умолчанию, нам необходимо всего-лишь внести правки под наш МК. Раскладка памяти и периферии во всех МК 1986ВЕ9х одинакова, поэтому и FLM им необходим один.
===Код FlashDev.c для 1986ВЕ9х===
Основные параметры взяты из спецификации. Таймаут для операций я указал в 5 секунд, надеюсь что этого достаточно. Параметры, указанные в этом файле, будут использоваться Keil при стирании и записи страниц памяти.
#include "FlashOS.H" // FlashOS Structures
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, // Driver Version, do not modify!
"StartMilandr 1986VE9x FlashInt", // Название, показывается при выборе алгоритма в FlashDownload.
ONCHIP, // Device Type - Внутренняя Flash
0x08000000, // Адрес начала Flash памяти
0x00020000, // Количество памяти в байтах (128kB)
0x1000, // Размер порции данных, которыми будет производиться запись
// Подойдет размер одной страницы Flash памяти
0, // Reserved, must be 0
0xFF, // Значение стертого байта памяти.
5000, // Таймаут 5 сек на запись страницы
5000, // Таймаут 5 сек на стирание сектора
// Размер и относительный адрес начала страниц
0x00001000, 0x00000000, // Sector Size 4kB (32 Pages)
SECTOR_END
};
===Код FlashDev.c для 1986ВЕ1Т/1986ВЕ3Т===
Для МК 1986Е1Т и 1986ВЕ3Т раскладка памяти также одинакова, как и само ядро Cortex-M1. Поэтому для этих МК собирается тоже один FLM на двоих. Помимо раскладки памяти следует учитывать вопросы тактирования, но эти МК переводятся на тактирование от HSI одинаково, поэтому проблемы в использовании одного FLM на двоих нет.
#include "FlashOS.H" // FlashOS Structures
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, // Driver Version, do not modify!
"StartMilandr 1986VE1/VE3 FlashInt", // Device Name
ONCHIP, // Device Type
0x00000000, // Device Start Address
0x00020000, // Device Size in Bytes (128kB)
0x1000, // Programming Page Size
0, // Reserved, must be 0
0xFF, // Initial Content of Erased Memory
5000, // Program Page Timeout 5 Sec
5000, // Erase Sector Timeout 5 Sec
// Specify Size and Address of Sectors
0x00001000, 0x00000000, // Sector Size 4kB (32 Pages)
SECTOR_END
};
Отдельно остановлюсь на последней строке. В начальном варианте проекта я неправильно понял назначение этих параметров. Исправиться помог Владимир из техподдержки Миландр, за что ему большое спасибо!
// Specify Size and Address of Sectors
0x00001000, 0x00000000, // Sector Size 4kB (32 Pages)
Здесь есть небольшая путаница. В терминах FLM, сектор - это часть памяти (адресного пространства от и до), которая может быть стерта за раз. В спецификации на микроконтроллер это соответствует странице памяти. Внутренняя память МК состоит из 32 страниц, каждую из которых можно стереть по отдельности. Если смотреть далее по спецификации, то каждая страница в свою очередь состоит из 4 секторов. Но эти сектора в памяти страницы - совсем не то, что подразумевает FLM. Итого, параметры означают следующее:
* 0x00001000 - размер одной страницы. Адреса с таким шагом будут подаваться в функцию EraseSector(adr). При записи программы, J-Flash или Keil считают сколько нужно стереть страниц и вызывают EraseSector() с адресами, кратными размеру страницы памяти микроконтроллера.
* 0x00000000 - стартовый адрес памяти, относительно адреса, указанного выше в структуре - Device Start Address.
Например, если зашиваемая программа занимает порядка 3 секторов, то будет вызвано стирание трех страниц (на примере 1986ВЕ9х):
EraseSector(0x08000000);
EraseSector(0x08000000 + 1 * 0x00001000);
EraseSector(0x08000000 + 2 * 0x00001000);
Остальные страницы не будут стираться чтобы сэкономить ресурс flash памяти, который составляет порядка 10-20 тысяч циклов перезаписи. Причем Keil, в отличие от J-Flash, поступает еще хитрее. Keil сначала проверяет страницу на стертость, и если страница стерта, то он пропускает стирание этой страницы и переходит к следующей. J-Flash же честно вызывает EraseSector() для каждой страницы, вне зависимости от того пустая она или нет.
Необходимость с указанием размера и относительного адреса связана с тем, что память может быть устроена по-разному. Например, в этом примере от [[http://www.keil.com/pack/doc/CMSIS/Pack/html/flashAlgorithm.html#AddFPA|ARM]], представлена память, где размер страниц в адресном пространстве разный:
struct FlashDevice const FlashDevice = {
...
0x00000000, // Device Start Address
0x00040000, // Device Size in Bytes (256kB)
...
// Specify Size and Address of Sectors
0x002000, 0x000000, // Sector Size 8kB (8 Sectors)
0x010000, 0x010000, // Sector Size 64kB (2 Sectors)
0x002000, 0x030000, // Sector Size 8kB (8 Sectors)
SECTOR_END
};
В этом примере показана непрерывная область памяти с адресами от 0x00000000 до 0x00040000, где
* от 0x00000000 до 0x00010000, расположено 8 страниц размером по 0x002000 - 8кБ.
* от 0x00010000 до 0x00030000, расположено 2 страницы размером по 0x010000 - 64кБ.
* от 0x00030000 до 0x00040000, расположено 8 страниц размером по 0x002000 - 8кБ.
На самом деле, средние большие страницы по 64КБ могут таковыми и не быть. Важно чтобы функция EraseSector() при обращении к ней с адресами 0х10000 и 0х20000 стирала по 64КБ.
====Реализация функций - FlashPrg.c====
Файл //FlashPrg.c// должен реализовать функции для работы FLM. Поскольку эти же функции мы будем использовать в тестовом проекте, то файл //FlashPrg.c// переносим в директорию src. И так как функции работы с EEPROM из файла MDR32F9Qx_eeprom.c работают со всеми МК, то файл //FlashPrg.c// потребуется всего один на все проекты.
//(Удаляем файл из проекта, переносим файл FlashPrg.c в папку src, подключаем файл к проекту из нового пути. В закладке настроек проекта С++ указываем путь к папке src.)//
Чтобы не перегружать статью, не буду приводить весь код функций. Остановлюсь только на особенностях.
Если вернуться к описанию вызовов функций FLM при прошивке ([[http://arm-software.github.io/CMSIS_5/Pack/html/algorithmFunc.html|link]]) то видно, что любая работа с памятью начинается с вызова функции //Init()// и заканчивается вызовом //UnInit()//.
===Init===
/*
* Parameter: adr: Device Base Address
* clk: Clock Frequency (Hz)
* fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
* Return Value: 0 - OK, 1 - Failed
*/
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
// Запрет всех прерываний
__disable_irq();
// Включение HSI
ClocksDeinit_HSI();
// Clock from HSI
MDR_RST_CLK->CPU_CLOCK = 0;
// EEPROM Clock On
if ((fnc == 1) || (fnc == 2))
MDR_RST_CLK->PER_CLOCK |= RST_CLK_PCLK_EEPROM;
return (0);
}
В коде данной функции необходимо учитывать, что в момент начала работы прошивальщика, МК может быть настроен произвольным образом. То есть работать на какой-то неизвестной частоте, с периферией и прерываниями. Следовательно необходимо все это отключить. \\
//(В первых версиях кода я этого не делал, поэтому прошивка завершалась успешно только со второго раза. Как предполагаю, с первого раза портилась текущая программа, а со второго раза не происходил запуск испорченной программы, МК оставался работать в настройках по сбросу, и тогда FLM отрабатывал полностью правильно.)//
Из входных параметров я использую только //fnc//, для того чтобы включить тактирование блока EEPROM, если будут операции стирания и записи. При верификации я читаю память напрямую, без блока EEPROM. Но если FLM будет модифицироваться для работы с информационной памятью или будет выставлена опция в коде READ_BY_EEPROM, то тактирование на RST_CLK_PCLK_EEPROM должно подаваться и в операциях верификации.
Переход на частоту HSI производится функцией //ClocksDeinit_HSI()//, которая реализована в //src/flmClock.c//.
===UnInit===
/*
* De-Initialize Flash Programming Functions
* Parameter: fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
* Return Value: 0 - OK, 1 - Failed
*/
int UnInit (unsigned long fnc) {
volatile uint32_t d;
MDR_EEPROM->CMD = 0;
MDR_EEPROM->KEY = 0;
MDR_RST_CLK->PER_CLOCK = 0x00000010;
// Update page cache
d =(*((volatile uint32_t *) PAGE1_ANY_ADDR));
// Update page cache
d =(*((volatile uint32_t *) PAGE2_ANY_ADDR));
return (0);
}
Функция //UnInit()// выключает тактирование с блока EEPROM, предварительно остановив работу блока через регистры. Исполнение алгоритма может сбиться или зависнуть. Если таймаут операции закончится, то прошивальщик Keil вызовет процедуру //UnInit()// в процессе работы для того, чтобы остановить исполнение.
//(В коде других производителей можно увидеть применение сторожевых таймеров, чтобы предотвратить возможные сбои при работе FLM. Например, применение FLM не к месту "убьет" тактирование ядра, тогда Jtag отвалится, и никакие UnInit() уже не помогут - спасет сторожевой таймер.)//
В конце кода //UnInit()// идет обращение в произвольным ячейкам в разных страницах памяти. Дело в том, что, например, в 1986ВЕ1Т, ускоритель Flash памяти после операции записи не обновляет свое содержимое из обновленной памяти и может вернуть предыдущее значение. Доступ к произвольным ячейкам должен заставить ускоритель обновить данные. По крайней мере, такой код был добавлен в официальный FLM для 1986ВЕ1Т - 1986BE1_rev2.FLM.
===EraseChip, EraseSector, ProgramPage===
Используя стандартные функции SPL по работе с EEPROM, реализовать следующие функции FLM не составляет труда.
// Выбор основной или инфо памяти
#ifdef SELECT_INFO_MEM
#define BANK_SELECTOR EEPROM_Info_Bank_Select
#else
#define BANK_SELECTOR EEPROM_Main_Bank_Select
#endif
/*
* Parameters: adr: Page Start Address
* sz: Page Size
* buf: Page Data
* Return Value: 0 - OK, 1 - Failed
*/
// Стирание всей памяти
int EraseChip (void)
{
EEPROM_EraseAllPages(BANK_SELECTOR);
return (0);
}
// Стирание Страницы
int EraseSector (unsigned long adr)
{
EEPROM_ErasePage(adr, BANK_SELECTOR);
return (0);
}
// Программирование слова, байтам требуется Swap.
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf)
{
uint32_t i;
for (i = 0; i < sz; i += 4)
EEPROM_ProgramWord(adr + i, BANK_SELECTOR, buf[i+3] <<24 | buf[i+2]<<16 | buf[i+1]<<8 | buf[i]);
return (0);
}
===BlankCheck, Verify===
Для реализации следующих двух функций я добавил функцию чтения слова //ReadWord()//. В этой функции выбирается чтение напрямую или через контроллер EEPROM. В функцию BlankCheck() передается параметр pat, это значение стертого байта из файла FlashDev.c. В текущем коде я им не пользовался, поскольку в микроконтроллерах Миландр стертое слово всегда равно 0xFFFFFFFF.
// Чтение слова напрямую или через контроллер EEPROM
uint32_t ReadWord(uint32_t Address)
{
#ifndef READ_BY_EEPROM
return *((volatile uint32_t *)(Address));
#else
return EEPROM_ReadWord(Address, EEPROM_Info_Bank_Select);
#endif
}
// Проверка что память очистилась
int BlankCheck (unsigned long adr, unsigned long sz, unsigned char pat)
{
uint32_t i;
volatile uint32_t value;
for (i = 0; i < sz; i +=4)
{
value = ReadWord(adr + i);
#ifdef FIX_DBL_READ
if (0xFFFFFFFF != value)
value = ReadWord(adr + i);
#endif
if (0xFFFFFFFF != value)
return 1;
}
return 0;
}
// Верификация записанной памяти, по словам
unsigned long Verify (unsigned long adr, unsigned long sz, unsigned char *buf)
{
uint32_t i;
//uint32_t addrI;
uint32_t* pData32 = (uint32_t*)buf;
volatile uint32_t value;
// Loop by Int
sz = sz >> 2;
for (i = 0; i < sz; ++i)
{
value = ReadWord(adr + (i << 2));
#ifdef FIX_DBL_READ
if (pData32[i] != value)
value = ReadWord(adr + (i << 2));
#endif
if (pData32[i] != value)
break;
}
return adr + (i << 2);
}
При отладке функций обнаружилось, что изредка чтение некоторых ячеек дает неправильный результат. Обычно это происходит в 1986ВЕ1Т после стирания всего чипа. Не вполне понял, с чем это связано, виноват ускоритель Flash или что-то иное, но добавление двойного считывания в случае сбоя помогло при прогоне тестов. Сбои прекратились, поэтому опция с двойным чтении осталась в коде. Позднее были собраны отдельные проекты с этой опцией, чтобы сравнить на сколько она необходима. Проекты и конечные FLM отличаются от тех, что с двойным чтением суффиксом _DR (DoubleRead) - например, //1986VE9x_FlashInt_DR.FLM//. Мнение о необходимости DoubleRead пока не сложилось.
=====Необходимость функций=====
Стоит отметить, что функции //BlankCheck()//, //EraseChip()// и //Verify()// не являются необходимыми. При отсутствии реализации, EraseChip() заменяется стиранием по секторам, вместо вызова Varify() прошивальщик сравнит CRC исходных и записанных данных. //(BlankCheck() видимо совсем может быть пропущено, альтернативы этой функции я не нашел.)//
=====Workspace и сборка проекта=====
Если все сделано правильно, получившийся проект должен собираться и выдавать файл FLM.
В директории FLM_Build я создал workspace для нескольких проектов. В каждом проекте будет собираться свой FLM. Вариантов проектов может быть много - под каждый МК, с различными опциями или настройками. Все проекты в одном workspace можно собрать разом, достаточно нажать кнопку //Batch Build// в панели Keil и на выходе получаем готовые FLM файлы.
Создается workspace в меню Keil - //Project - New Multy-Project Workspace...//, далее необходимо дать название - у меня получился //FLM_Build.uvmpw//. После задания имени откроется окно менеджера проектов, в котором можно выбрать проекты для добавление в workspace.
Активным может быть только один проект, для него открываются опции проекта и только его можно редактировать. Чтобы поменять активный проект, надо кликнуть на необходимый проект правой клавишей мыши и выбрать //Set as Active Project//.
Для добавления и удаления проектов необходимо правой мышью кликнуть на заголовок workspace и выбрать //Manage Multy-Project Workspace...//.
После добавления всех проектов в workspace нажимаем на панели кнопку //Batch Build//, при этом откроется окно, в котором необходимо выбрать проекты, которые будут собираться. Варианты с ARM7_ARM9 нам не нужны, остальные выбираем.
После сборки проектов //Build// в директории FLM_Build получаем файлы FLM.
{{prog:start:keil_buildflm.png}}
Далее осталось провести их проверку с Keil и c утилитой J-Flash.
=====Результат=====
Полученные FLM проверены на МК 1986ВЕ1Т, 1986ВЕ3Т и 1986ВЕ9х. Прошивка происходит, но не со 100% результатом. Иногда выдаются сбои верификации. Например, при прошивке через J-Flash выдается статус, что верификация провалена. При этом программа прошивается и работает. Если же запустить прошивку второй раз, то она не происходит, а выдается сообщение, что программа уже зашита и верификация зашитой программы прошла успешно.
Будем разбираться с этим дальше.
//(По окончании темы статья будет дополнена)//
Обратите внимание, в спецификации на микроконтроллеры указано - **"При стирании
информационной области автоматически стирается и основная"**.
Это совсем не очевидно и попалось на глаза случайно. Стоит иметь это ввиду при работе с информационной памятью.
===Тестовые проекты===
Тестовые проекты описывать не буду, код представлен в директории test. Проекты имитируют последовательность вызовов функций FlashPrg.c прошивальщиком Keil. Это обычные проекты для работы с МК с той лишь особенностью, что они собраны для запуска из ОЗУ и поэтому работают только в режиме отладки. Но, собственно, этого достаточно.