Инструменты пользователя

Инструменты сайта


prog:start:newflm

Создание FLM

FLM - это проект для Keil, который среда загружает в МК по интерфейсу Jtag или SWD. Файл FLM должен реализовать несколько функций, которые будут вызываться при прошивке и которым будут передаваться данные для записи в память. FLM может быть различное множество - для внутренней Flash, для записи во внешнюю память ОЗУ/ПЗУ по внешней шине или SPI, и т.д. То есть это просто проект, который принимает данные извне и знает что и как с ними делать, предварительно настроив необходимую периферию.

В отличие от официальных версий, FLM из текущего проекта будут поддерживать стирание памяти по секторам. Что позволит использовать их совместно с утилитой 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, эта возможность отключена.

Целиком проект доступен на 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 файле описаны здесь (про функции подробнее дальше). Для того, чтобы отладить их реализацию, я создаю отдельный тестовый проект для каждого МК, реализую указанные в ссылке варианты вызовов функций, проверяю на МК вживую и отлаживаю. Эти же реализации функций затем используются в проектах, которые собирают сами 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 предложит установить "Legacy Support for ARM7, ARM9 & Cortex-R". Этого не требуется, нажимаем Cancel. У меня уже установлен Legacy support for ARM Cortex-M devices, как рекомендовалось в статье - Установка Keil и SPL Milandr. Если поддержка Cortex-M не установлена, то необходимо ее установить.

В проекте используется всего два исходных файла - FlashDev.c и FlashPrg.c. Дополнительно к этим файлам необходимо подключить исходники из директории scr:

  1. MDR32F9Qx_eeprom.c
  2. 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() для каждой страницы, вне зависимости от того пустая она или нет.

Необходимость с указанием размера и относительного адреса связана с тем, что память может быть устроена по-разному. Например, в этом примере от 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 при прошивке (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.

Далее осталось провести их проверку с Keil и c утилитой J-Flash.

Результат

Полученные FLM проверены на МК 1986ВЕ1Т, 1986ВЕ3Т и 1986ВЕ9х. Прошивка происходит, но не со 100% результатом. Иногда выдаются сбои верификации. Например, при прошивке через J-Flash выдается статус, что верификация провалена. При этом программа прошивается и работает. Если же запустить прошивку второй раз, то она не происходит, а выдается сообщение, что программа уже зашита и верификация зашитой программы прошла успешно.

Будем разбираться с этим дальше.

(По окончании темы статья будет дополнена)

Обратите внимание, в спецификации на микроконтроллеры указано - "При стирании информационной области автоматически стирается и основная".

Это совсем не очевидно и попалось на глаза случайно. Стоит иметь это ввиду при работе с информационной памятью.

Тестовые проекты

Тестовые проекты описывать не буду, код представлен в директории test. Проекты имитируют последовательность вызовов функций FlashPrg.c прошивальщиком Keil. Это обычные проекты для работы с МК с той лишь особенностью, что они собраны для запуска из ОЗУ и поэтому работают только в режиме отладки. Но, собственно, этого достаточно.

prog/start/newflm.txt · Последнее изменение: 2022/04/03 23:09 (внешнее изменение)