Содержание

OTP_Test_VE8 - программирование OTP в 1986ВЕ8Т

Вместо привычной флеш памяти в микроконтроллере 1986ВЕ8Т реализована однократно программируемая память ОТР - (One-Time Programable). Такая память бывает двух типов - Fuse и AntiFuse. Fuse переводится как предохранитель, что намекает на способ программирования. В памяти типа Fuse программирование бита сводится к пережиганию перемычки, а в памяти AntiFuse - к созданию пробоя подзатворного диэлектрика.

Красивые картинки про память и многое полезное можно найти в данной статье - The Benefits Of Antifuse OTP.

В обоих случаях изначально биты имеют значения 0, а прожигаются только те биты, которые должны стать "1". Согласно приведенной статье, прожиг бита памяти Fuse можно сделать только один раз. Если после единичного прожига бит не стал читаться как "1", то память бракуется. В отличие от Fuse, бит памяти типа AntiFuse можно прожигать несколько раз. При прожиге происходит пробой подзатворного диэлектрика, если пробой будет небольшой, то его можно и нужно "прожигать" повторно, чтобы образовался надежный контакт. Если контакт оставить плохой, то память не сможет работать на заявленных скоростях.

Программирование, допрограммирование, верификация

Все вышесказанное объясняет алгоритм программирования памяти ОТР в микроконтроллере 1986ВЕ8Т. Согласно спецификации, сначала необходимо провести первичный прожиг бит равных "1" - "фаза программирования". После этого запускается "фаза допрограммирования", когда считываются текущие значения в ячейках и если где-то "1" не прописалась, то запускается процедура программирования для данного бита. После окончания допрограммирования необходимо проверить, на сколько качественно прожглись каналы в подзатворном диэлектрике. Для этого запускается этап "верификации", который на максимально заявленной для памяти частоте сверяет значения ячеек памяти. Если каналы будут слабые, то при высокой скорости обращения будут возникать сбои, память будет читаться с ошибками, т.е. не будет совпадать со значениями, которые в нее были записаны. Описанная выше процедура составляет один цикл программирования памяти. Гарантированный компанией "Миландр" коэффициент программируемости составляет 0,7. Т.е. при программировании 100 микросхем будут успешно запрограммированы только 70, а 30 микросхем запрограммировать не удастся - будут ошибки. Но это гарантируемый коэффициент, на самом деле процент программируемых микросхем гораздо выше. Каждый производитель оставляет себе некоторый запас.

В случае, если в первом цикле программирования верификация выявила наличие ошибок, то есть возможность запустить повторный цикл. Но во вторичном цикле необходимо исключить операцию программирования. Это связано с тем, что вторичный прожиг уже пробитого бита не желателен. Поэтому второй цикл программирования должен состоять только из этапов допрограммирования и верификации. Производитель не рекомендует использовать более двух циклов программирования, предлагая в случае неудачи второго цикла браковать микроконтроллер.

Стоит отметить, что если в фазе допрограммирования сразу проверять значение только что про прожженного бита, то считать "0" вместо положенного "1" считается нормальным. Дело в том, что для памяти есть настройки, которые задают "стойкость" значений к считыванию. Эти параметры называются репликами и задаются в регистре TUNING блока MDR_OTP. В данном регистре можно задать, какое из значений "0" или "1" будет читаться "легче". Так, например, если задать, чтобы легче читался "0", то чтение "1"-цы будет затруднено. К сожалению, я не обладаю знаниями, чем эти "легче/тяжелее" обусловлены физически, но смысл здесь в том, что для прожига необходимо выставить наибольшее сопротивление к чтению, чтобы записанный бит читался "1"-цей только в том случае, когда уже диэлектрик пробит основательно. Это гарантирует, что при обычных репликах чтения, которые используются на этапе верификации, память будет читаться на максимальной частоте успешно. В итоге, только этап верификации показывает, было ли программирование успешно!

Кроме этого, чип бракуется, если на этапе допрограммирования будет выявлено, что какой-то из битов, который должен быть нулем оказался вдруг "1"-ей. Это может произойти, например, если данные начинают писаться в память, которая уже была записана раннее другими данными. Когда вместо нового "0"-ля, в памяти уже находится старая "1"-ца, то исправить такое положение нельзя. Обратите, пожалуйста, внимание, что Нули в памяти находятся изначально, записываются только "1"-цы!

(Дополнительную информацию можно найти здесь - Программирование 1645РТ3У)

Алгоритм программирования

Сравнивая описание этапов программирования и допрограммирования, можно заметить, что они мало отличаются. Собственно, различий всего два:

  1. Допрограммирование считывает текущее значение слова из памяти и затем программирует только недостающие 1-цы. Программирование же пишет 1-цы всегда, поскольку не читает текущее значение.
  2. Программирование записывает бит только один раз, допрограммирование же пытается сделать это до 40 раз.

Если свести описания этапов на одну блок-схему, то получается такая картина:

По схеме видно, что различия крайне незначительны и можно реализовать оба этапа одной функцией. При этом "Различие 2" решается передачей в функцию количества повторов программирования бита - 1 цикл для программирования и 40 для допрограммирования. Остается "различие 1" с присутствием чтения текущего значения при допрограммировании. Но будет ли хуже, если программирование тоже будет читать текущее значение ячейки и писать только, если есть незаписанные 1-цы?

Изначально предполагается, что чип чист, и вся память 0-вая, поэтому нет чтения в программировании. В допрограммировании же важно исключить вторичный прожиг уже пробитого бита, поэтому чтение необходимо. Судя по всему, лишнее чтение ничем этапу программирования не навредит. Репликами чтение 0-ей выставлено максимально легким, т.е. нет шансов, что "0" друг прочтется "1"-ей и будет пропущено программирование какого-либо бита из-за того, что добавилось предварительное чтение. Кроме этого, если по ошибке произойдет запуск программирования для уже записанной памяти, то это исключит повторную запись уже пробитого бита.

В целом, я не вижу причины делать две отдельные функции для этапов программирования и допрограммирования. Достаточно реализовать их в одной функции, задав на вход лишь количество допустимых повторов. В библиотеке Pack_V6 это сделано так:

//  Функция программирования слова OTP
bool MDR_OTP_ProgWordEx(uint32_t addr, uint32_t data, uint32_t cycleCount);

//  Программирование слова - 1 цикл
__STATIC_INLINE 
void MDR_OTP_ProgWord(uint32_t addr, uint32_t value) { MDR_OTP_ProgWordEx(addr, value, 1); }

//  Допрограммирование слова - 40 циклов
#define  MDR_OTP_REPROG_CNT   40
__STATIC_INLINE 
void MDR_OTP_RepProgWord(uint32_t addr, uint32_t value)  { MDR_OTP_ProgWordEx(addr, value, MDR_OTP_REPROG_CNT); }

Листинг самой функции я приводить не буду, исходники доступны на GitHub, драйвер MDR_OTP - MDR_OTP_VE8x.h и MDR_OTP_VE8x.c .

С помощью драйвера MDR_OTP запись памяти выглядит так, отрывок из проекта описанного ниже:

void OPT_WriteTestData(void)
{
  MDR_OTP_Enable();                            // - Выключение доступа к контроллеру OTP, KEY
  MDR_OTP_ProgBegin_ByClockCPU_Hz(freqCPU_Hz); // - Расчет задержек, выставление минимального DUcc,
                                               //   задержка HV_PE, выставление реплик в TUNING
  MDR_OTP_ProgWord(OTP_TEST_PROG_ADDR, OTP_TEST_PROG_DATA);   // Программирование слова
  ...
  ...  
  ...

  MDR_OTP_ProgEnd();                           // - реплики и DUcc в состояние по умолчанию
  MDR_OTP_Disable();                           // - выключение доступа к контроллеру OTP, KEY
}

Внешнее напряжение программирования 7,2 В должно быть задано до исполнения данного кода! Повышенное напряжение необходимо, чтобы пробить диэлектрик, для этих же целей в функции MDR_OTP_ProgBegin() понижается питание самой микросхемы через подстройку внутренних LDO. После модификации LDO выдерживается необходимая задержка между подачей питания HV и входом в режим программирования - бит PE.

Пример OTP_Test_VE8

Данный пример был реализован, чтобы проверить работоспособность драйвера MDR_OTP. В примере используются все три кнопки на отладочной плате:

Для индикации используется три светодиода:

Тестовые адрес и данные

Слова в проекте пишутся, начиная с конца памяти ОТР, чтобы оставить начальные адреса для рабочей программы. Пишется всего одно слово, на нем проверяется, как отрабатывает запись и чтение. Для записи следующего слова необходимо исправить в коде значение OTP_TEST_TEST_IND, потому что оно задает адрес:

//  Увеличивать после каждой записи - сдвиг на чистую память с конца ОТР.
//  Минимальное значение = 1
#define OTP_TEST_TEST_IND          5

#define OTP_TEST_ADDR_END          0x01020000
#define OTP_TEST_PROG_ADDR         (OTP_TEST_ADDR_END - (OTP_TEST_TEST_IND * 4))

В тесте был опробован прожиг от трех источников тактирования, данные, записываемые при каждом прожиге, тоже менялись:

//  Значения записываемые/записанные в память в порядке OTP_TEST_TEST_IND
//  1: OTP_TEST_TEST_IND = 1 от HSE0_PLL_Max
//  #define OTP_TEST_PROG_DATA         0x13768421
//  2: OTP_TEST_TEST_IND = 2 от HSE0_PLL_Max
//  #define OTP_TEST_PROG_DATA         0x87245687   
//  3: OTP_TEST_TEST_IND = 3 от HSI_Trim (задержки точные)
//  #define OTP_TEST_PROG_DATA         0xABCDEFED
//  4: OTP_TEST_TEST_IND = 4 от HSI без Trim (задержки не точные)
//  #define OTP_TEST_PROG_DATA         0x12345678
//  5: OTP_TEST_TEST_IND = 5 от HSE1 Gen 25MHz
    #define OTP_TEST_PROG_DATA         0x9ABCDEF

В сущности не важно, на какой частоте происходит прошивка, все варианты отработали с первого раза - с первого нажатия на кнопку Key1. Важно правильно выдержать необходимые задержки в циклах программирования! Рекомендуемая частота прошивки ~24МГц необходима лишь для проведения этапа верификации. Ведь читать память необходимо на ее максимальной рабочей частоте. В данном примере это возможно только в варианте тактирования от внешнего генератора.

Реализация задержек

Необходимые задержки указаны в спецификации на стр. 215, таблица 36:

Название Имя Мин значение Кол. тактов
мкс 8 МГц 25 МГц 40 МГц 64 МГц
Режим записи - бит PE
Предустановка HV перед PE t_HV_PE 10000 80000 250000 400000 640000
Предустановка PE перед DATA t_PE_D 300 2400 7500 12000 19200
Предустановка ADR перед DATA t_A_D 300 2400 7500 12000 19200
Удержание ADR после DATA t_D_A 5 40 125 200 320
Время программирования t_Prog 3000 24000 75000 120000 192000
Задержка между битами t_LD 5 40 125 200 320
Удержание PE после DATA t_D_PE 0 0 0 0 0
Режим чтения - бит SE
Предустановка ADR перед SE t_A_SE 0.005 0,04 0,125 0,2 0,32
Удержание ADR после SE t_SE_A 0 0 0 0 0
Длительность HSE t_SE 0.01 0,08 0,25 0,4 0,64

По таблице видно, что для всех допустимых частот ядра достаточно лишь четырех задержек - на 10мс, 3мс, 300мкс и 5мкс. Остальные задержки составляют меньше чем один такт ядра, поэтому заботиться об их выдержке нет необходимости.

Задержка 5мкс между записью нескольких бит подряд не пригодилась. Дело в том, что на блок-схемах в спецификации описан режим программирования, в котором каждый бит записывается и тут же читается 5 раз, чтобы убедиться, что он прошит. Если бит не прошит, то он снова прожигается, вплоть до 30 раз. На диаграмме же сигналов в спецификации "Рисунок 57 – Временная диаграмма процесса программирования OTP памяти" представлен режим, в котором биты записываются подряд, а затем видимо целиком происходит чтение всего слова. По тексту спецификации подобный режим не описан, но реализован у Vasili с форума в его программе прошивки OTP по UART - PRG_OTP_UART.

Драйвер MDR_OTP был реализован согласно блок схем из спецификации и примера полученного от тех-поддержки. Диаграмма задержек при записи одного бита получается такая:

На диаграмме есть не упоминающиеся в спецификации задержки, это:

Для расчета задержек в функцию MDR_OTP_ProgBegin_ByClockCPU_Hz() передается текущая частота процессора, которая определяется функцией MDR_CPU_GetFreqHz(). Важно лишь в MDR_ConfigVE8.h прописать правильные частоты для подключенных внешних HSE0 и HSE1.

В качестве альтернативы, расчет задержек можно сделать отдельной функцией MDR_OTP_GetProgDelays() и получить из нее структуру с задержками, которая затем подается на вход MDR_OTP_ProgBegin(). Все тоже самое делает инлайн функция MDR_OTP_ProgBegin_ByClockCPU_Hz(), чтобы не писать это отдельно.

  #define MDR_OTP_DELAY_US_HV_PE   10000
  #define MDR_OTP_DELAY_US_A_D       300
  #define MDR_OTP_DELAY_US_PROG     3000

  typedef struct {
    uint32_t delay_HV_PE;
    uint32_t delay_A_D;
    uint32_t delay_Prog;
  } MDR_OTP_ProgDelays;
  
  //  Вычисление задержек
  MDR_OTP_ProgDelays   MDR_OTP_GetProgDelays(uint32_t freqCPU_Hz);  
  
  //  Инициализация программирования - снижение DUcc, выставление Tuning
  void MDR_OTP_ProgBegin(const MDR_OTP_ProgDelays *progDelays);

  //  Все описанное вместе
  __STATIC_INLINE void MDR_OTP_ProgBegin_ByClockCPU_Hz(uint32_t freqCPU_Hz) 
  { 
    MDR_OTP_ProgDelays delays = MDR_OTP_GetProgDelays(freqCPU_Hz);
    MDR_OTP_ProgBegin(&delays); 
  }

Задержки рассчитываются под функцию MDR_Delay(), реализация которой была описана ранее в данной статье - "Функция задержки и особенности её реализации в ассемблере".

Значения в таблице определяют минимальные величины задержек, следовательно, их важно не сделать короче. Самая важная задержка - это время программирования. Она единственная, для которой определено максимальное значение. Это необходимо для того, чтобы ограничить время воздействия на ячейку повышенного напряжения. Иначе, вероятно, может случиться деградация структур в топологии, тогда могут пострадать и соседние ячейки. Другие же задержки лишь означают, что следующий по диаграмме сигнал необходимо выставить не раньше, чем к этому будет готов контроллер памяти.

Для того чтобы проверить, как отрабатываются задержки данной функцией в проекте, используется кнопка KEY3. При нажатии на данную кнопку, на светодиоде LED3 начинают переключатся уровни напряжений с заданными задержками. Вот, например, какие возникают задержки при тактировании от HSI со значением Trim по умолчанию:

Как видно, данные задержки превышают минимальные значения, т.е. по-существу, являются допустимыми. Время программирования t_Prog по спецификации составляет от 3 до 7мс. Что тоже не нарушается при таких задержках. Поэтому при одном из запусков проекта прошивались ячейки при тактировании от HSI без подстройки. Для полного соответствия проводилась прошивка и с подстроенным HSI, задержки тут имеют более точные значения:

После вторичного нажатия на кнопку KEY3 режим вывода задержек отключается.

Запуск проекта - предварительные проверки

Для запуска проекта к демо-плате необходимо подключить источник питания на 7,2В - это середина из разрешенного диапазона 7,0-7,4В. В нашем случае это был источник, непосредственная подача питания от которого осуществляется по нажатию кнопки. Согласно порядку подачи напряжений, сначала необходимо запитать плату от рабочего источника, а уже затем, когда дело дойдет до прошивки памяти, подать на плату напряжение программирования. Порядок снятия напряжений обратный - сначала необходимо отключить источник на 7,2В, потом выключить плату.

Для подачи напряжения программирования на плате есть специальный разъем HV:

otp_write_board_min.jpg

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

Проверка снижения питания (Опционально)

Прежде чем подать на плату повышенное напряжение, можно убедиться, что функция MDR_OTP_ProgBegin_ByClockCPU_Hz() действительно понижает напряжение DUcc. Для этого необходимо (см. код ниже):

  1. Закомментировать функцию программирования MDR_OTP_ProgWord(), а до и после MDR_OTP_ProgBegin_ByClockCPU_Hz() поставить breakpoint-ы.
  2. Проект запускается из под ОЗУ, поэтому, запустившись с отладчиком, нажимаем F5 чтобы начать исполнение.
  3. Нажимаем KEY1 на плате и попадаем на первый брейк-поинт - перед MDR_OTP_ProgBegin_ByClockCPU_Hz(). Замеряем значение DUcc на любом выводе DUcc на плате (см. фото). Напряжение должно быть порядка 1,8В.
  4. Нажимаем на F5 и попадаем на второй бейк-поинт. Снова замеряем напряжение, оно должно понизиться. В нашем тесте оно составило 1,67В.
  5. (Функция программирования MDR_OTP_ProgWord() закомментирована - не исполняется.)
  6. Нажимаем F10 чтобы выполнить MDR_OTP_ProgEnd(). Проверяем, что напряжение вернулось в исходное значение.
  7. (Раскомментируем функцию MDR_OTP_ProgWord(), чтобы в дальнейшем программирование работало.)
void OPT_WriteTestData(void)
{
  MDR_OTP_Enable();  // Breakpoint сюда, чтобы увидеть начальный уровень DUcc
  MDR_OTP_ProgBegin_ByClockCPU_Hz(freqCPU_Hz);
//  MDR_OTP_ProgWord(OTP_TEST_PROG_ADDR, OTP_TEST_PROG_DATA); - отключаем программирование
  MDR_OTP_ProgEnd();  // Breakpoint сюда, чтобы увидеть снижение DUcc
  MDR_OTP_Disable(); 
}

Проверка выдержки задержек

Код библиотеки может меняться, могут поменяться функции настройки частоты, или компилятор сформирует прошивку несколько по другому. Все это может привести к тому, что данный проект перестанет отрабатывать так, как он работает сейчас, при написании данной статьи. Поэтому для каждого источника тактирования следует проверить, что задержки формируются правильно!

В проекте проверяется прошивка при тактировании от источников:

Выбор частоты для которой собирается проект делается условной компиляцией. Необходимо чтобы был выбран только один вариант из:

#define OTP_PROG_WITH_HSE0_MAX_CLOCK  1
#define OTP_PROG_WITH_HSI             0
#define OTP_PROG_WITH_HSE1_GEN_25MHZ  0

Библиотека должна знать, какие частоты подключены на HSE0 и HSE1, ведь снаружи могут быть подключены источники и с другими частотами. Значения внешних частот должны быть указаны в файле MDR_ConfigVE8.h. Это необходимо чтобы задержки из реальных величин времени пересчитывались в циклы задержки функции MDR_Delay() правильно - MDR_Delay - Функция задержки и особенности её реализации в ассемблере.

MDR_ConfigVE8.h
  //================  Параметры источников частоты ================
  //  Internal Generators
  #define HSI_FREQ_HZ       8000000UL

  //  External Generators
  #define HSE0_FREQ_HZ      10000000UL
  ...
  #define HSE1_FREQ_HZ      25000000UL
  ...

Чтобы измерить задержки:

  1. Запускаем отладку, нажимаем F5 для запуска исполнения. При этом начнет мигать LED1 (VD7).
  2. Нажимаем Key3 и видим, что начинает тускло гореть светодиод LED3 (VD9). Это говорит о том, что переключается сигнал на входе управляющем данным светодиодом. Уровни сигнала выдерживаются согласно трем задержкам.
  3. Измеряем сигнал на выводе плюс данного светодиода - должны получиться картинки, как те, что были представлены в предыдущем разделе. Измеряем задержки аналогично. Если задержки неправильные, ищем, в чем проблема - переходить к прошивке с неправильными задержками нельзя.
  4. Снова нажимаем на Key3, программа выходит из режима отображения задержек.

Если задержки получились неправильные необходимо проверить правильно ли указана частота в MDR_ConfigVE8.h и такая ли частота подана снаружи. Если все правильно, то необходимо проверить пересчет задержек для MDR_Delay() и значения вроде DELAY_LOOP_CYCLES_ASM. Можно перейти на вариант задержек через DWT - см. MDR_Delay - Функция задержки и особенности её реализации в ассемблере.

Если задержки правильные, то можно переходить в программированию ячейки ОТР.

Запуск проекта - Прошивка

Для прошивки ячейки памяти в проекте предлагается такой алгоритм действий:

  1. Включить плату.
  2. Войти в режим отладки, запускаем по F5. Начинает мигать LED1 (VD7), показывая, что код main исполняется.
  3. Нажать KEY2 (Read), при этом должен загореться светодиод LED3 (VD9) Error. Потому что ячейка памяти еще не прописана и содержит пустое значение 0х00000000.
  4. Теперь включить подачу напряжения программирования - 7,2В.
  5. Нажать KEY1 (Write) - загораются все три светодиода. Меньше чем через секунду светодиоды погаснут - программирование закончилось.
  6. Нажать KEY2, чтобы убедиться, что значение прописалось. Если значение правильное, то загорится LED2 (OK), если данные не совпали - LED3 (Error).
  7. Если данные не совпали, горит LED3, то повторяем запись - нажимаем KEY1. Также можно посмотреть отладчиком, насколько отличаются данные, какие биты не прописались.
  8. Отключить источник 7,2В.
  9. Выйти из режима отладки.
  10. Отключить питание платы. Выжидаем некоторое время, чтобы МК полностью сбросился.
  11. Включить снова плату, войти в режим отладки, нажать KEY2 - убедиться, что память содержит значение в нее записанное - должен гореть LED2 (OK).

При запусках проекта мы не столкнулись с ошибками при записи, память прописывалась с первого раза, на какой бы частоте мы ее не прописывали. Но в данном проекте нет как такового этапа верификации. Память здесь читается через регистровый доступ чтобы не было выхода в HardFault из-за ошибок ЕСС, при этом она фактически читается не при 25МГц и не через необходимые шины. Поэтому в данном примере качество "прожига" оценить нельзя.

Для прошивки следующего слова необходимо увеличить OTP_TEST_TEST_IND и можно поменять слово данных для разнообразия.

Не стоит пытаться дописать новые биты "1" в эту же ячейку памяти, потому что для нового значения слова изменится и ЕСС, для которого скорее всего потребуется изменить уже прописанный "0" на "1", что невозможно. Но с другой стороны, если дописываемый бит будет всего один, то это даст сбой по единичной ошибке ЕСС, которая будет аппаратно парирована в случае чтения данного слова по шине. Это можно использовать для проверки того, как отрабатывает контроллер ошибок FT_CNTRL, как генерируются события ошибки, прерывания и прочее.

Выводы

Запуск проекта показал, что:

  1. Функции работы с OTP 1986ВЕ8Т драйвера MDR_OTP отрабатывают правильно.
  2. Прошивать память можно на любой частоте, необходимо лишь выдержать правильно задержки. (Но для верификации необходима все-таки частота 25МГц! Верификация в данном примере не рассматривалась.)

Следующим шагом будет написать на основе функций драйвера MDR_OTP несколько FLM и прошить какой-нибудь проект, тоже куда-нибудь в конец памяти - статья, "OTP_FLM_VE8 - Реализация FLM для OTP в 1986ВЕ8Т"

Ссылка на проект - GitHub