======Создание 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. Это обычные проекты для работы с МК с той лишь особенностью, что они собраны для запуска из ОЗУ и поэтому работают только в режиме отладки. Но, собственно, этого достаточно.