[i] Внешняя шина с последовательным ECC в К1986ВЕ8Т

ID статьи: 24422
Дата последнего изменения: 26.11.2025 17:27:51

Рассмотрим, как проинициализировать внешнюю память, чтобы не возникали ошибки ECC, на примере включения микроконтроллера К1986ВЕ8Т с микросхемой памяти (асинхронного статического ОЗУ) К1645РУ5У на отладочной плате.

В проекте используется три кнопки, при нажатии на которые происходит:

  1. Key1: Портится согласованное состояния данных и ECC. Выключается режим ECC и вся память (область данных и область значений ЕСС) прописывается индексами.
  2. Key2: В режиме с ECC инициализируется память данных, область ЕСС при этом инициализируется автоматически. Последующее считывание показывает верность данных и отсутствие увеличения счетчиков ошибок.
  3. Key3: В режиме без ECC вносится одинарная и двойная ошибка. Затем в режиме с ECC проверяется, что одинарная ошибка исправляется при чтении автоматически, при этом увеличивается счетчик одинарных ошибок. Двойная ошибка не может быть исправлена. После этого обе ошибки перезаписываются в режиме с ЕСС записью 32-битных значений.

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

  1. VD7: Загорается всегда - исполнение закончено.
  2. VD8: Тест по Key2 или Key3 прошли успешно.
  3. VD9: Рассогласование данных по Key1 выполнено.

Начальная информация

Микроконтроллер К1986ВЕ8Т является радиационно стойким. Вместо электрически перепрограммируемой Flash-памяти внутри реализована однократно программируемая память (OTP). Биты каждого слова максимально разнесены по площади кристалла так, что при точечном внешнем воздействии пострадает не все слово, а только часть бит различных слов. Введение кодирования по Хэммингу делает возможным в таком случае восстановить единичные сбои, что позволяет сохранить работоспособность микроконтроллера. Этот же подход позволяет обнаруживать ошибки при сбоях или наводках на шинах и прочих компонентах микросхемы.

Для возможности восстановления слова данных для каждой комбинации адреса и данных по этому адресу высчитывается специальное слово - ECC. Для вычисления ЕСС необходим адрес 32-бита и данные 32-бита. В итоге получается 8 битное значение ECC. По значению ECC можно узнать, какой бит в данных или адресе окажется ложным в случае одиночной ошибки. Если ложными окажутся несколько битов, то это тоже определяется по ECC.

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

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

То есть если потребуется записать только один байт по какому-то адресу, то это произойдет в два этапа:

Но в случае, когда на шину пишутся сразу 32-х битные данные, не требуется считывание, поскольку новое значение ECC может быть высчитано сразу. Этим необходимо пользоваться для того, чтобы проинициализировать начальное состояние внешней памяти. То есть, при записи всего объема памяти будут записаны все данные и соответствующие им верные ЕСС. После этого с памятью можно работать в любом режиме - считывать/писать байты, 16-разрядные слова или 32-битные значения. Ошибки возникать не будут, и счетчики ошибок перестанут расти.

Если бы память не была проинициализирована, то при считывании постоянно возвращались бы несогласующиеся с ECC данные, при этом генерировались бы флаги ошибок, и данные пытались восстанавливаться.

В независимости от разрядности шины данных, начальная инициализация памяти в режиме с ECC должна производиться 32-х разрядными данными! Контроллер шины сам разобъет эти 32-битные записи на необходимые транзакции с разрядностью шины данных (серия записей по 8, 16 или 32 бита).

Фрагмент проекта представлен в фрагменте кода 1:

// Функция записи 32-х разрядными значениями индекса
void Fill_Data32_ByInd(uint32_t starAddr, uint32_t count)
{
  uint32_t* addr;
  uint32_t i = 0;

  addr = (uint32_t*)starAddr;
  for (i = 0; i < count; ++i)
    *addr++ = i;
}

#define RGN0_StartAddr 0x10000000 // Начало региона данных с контролем ECC
#define RGN0_EccAddr 0x10030000 // Начало области хранения значений ECC
#define ECC_NUM_BYTES 0x30000 // Количество данных в регионе в байтах
#define ECC_NUM_WORDS (ECC_NUM_BYTES >> 2) // Количество данных в 32-битных значениях

int main(void)
{
  ...

  // Вызов в main, инициализация памяти по нажатию на кнопку Key2.
  Fill_Data32_ByInd(RGN0_StartAddr, ECC_NUM_WORDS);
  Fill_Data32_ByInd(RGN0_StartAddr, ECC_NUM_WORDS); // Повторная инициализация для парирования ошибки
 ...
}

Фрагмент кода 1.

Включение микроконтроллера и настройка выводов

От того как произведено включение микроконтроллера и микросхем памяти, зависит код настройки выводов и контроллера шины. Подключение, реализованное на демоплате, представлено на рисунке 1.


Рисунок 1 - Подключение микроконтроллера К1986ВЕ8Т и микросхемы памяти К1645РУ5У.

По рисунку 1 видно, какие выводы микроконтроллера в какие функции должны быть настроены. Код настройки выводов GPIO приведен в фрагменте кода 2:

#define UNLOCK_KEY 0x8555AAA1

void ExtBus_InitPins_A19_D8(void)
{
 PORT_InitTypeDef PORT_InitStructure;

 CLKCTRL_PER0_CLKcmd(CLKCTRL_PER0_CLK_MDR_PORTC_EN, ENABLE);
 CLKCTRL_PER0_CLKcmd(CLKCTRL_PER0_CLK_MDR_PORTD_EN, ENABLE);
 CLKCTRL_PER0_CLKcmd(CLKCTRL_PER0_CLK_MDR_PORTE_EN, ENABLE);

 PORTC->KEY = UNLOCK_KEY;
 PORTD->KEY = UNLOCK_KEY;
 PORTE->KEY = UNLOCK_KEY;

 PORT_StructInit(&PORT_InitStructure);
 
// DATA BUS
 // Data[0..1]
 PORT_InitStructure.PORT_Pin = (PORT_Pin_30|PORT_Pin_31);
 PORT_InitStructure.PORT_SFUNC = PORT_SFUNC_2;
 PORT_InitStructure.PORT_SANALOG = PORT_SANALOG_DIGITAL;
 PORT_InitStructure.PORT_SPWR = PORT_SPWR_10;
 PORT_Init(PORTD, &PORT_InitStructure);
 // Data[2..7]
 PORT_InitStructure.PORT_Pin = (PORT_Pin_0|PORT_Pin_1|PORT_Pin_2|PORT_Pin_3|PORT_Pin_4|PORT_Pin_5);
 PORT_InitStructure.PORT_SFUNC = PORT_SFUNC_2; PORT_InitStructure.PORT_SANALOG = PORT_SANALOG_DIGITAL;
 PORT_InitStructure.PORT_SPWR = PORT_SPWR_10; PORT_Init(PORTE, &PORT_InitStructure);

 // ADDR BUS
 // Addr[0..1]
 PORT_InitStructure.PORT_Pin = (PORT_Pin_30|PORT_Pin_31);
 PORT_InitStructure.PORT_SFUNC = PORT_SFUNC_2;
 PORT_InitStructure.PORT_SANALOG = PORT_SANALOG_DIGITAL;
 PORT_InitStructure.PORT_SPWR = PORT_SPWR_10;
 PORT_Init(PORTC, &PORT_InitStructure);
 // Addr[2..18]
 PORT_InitStructure.PORT_Pin = ( PORT_Pin_0 |PORT_Pin_1 |PORT_Pin_2 |PORT_Pin_3 |
                                 PORT_Pin_4 |PORT_Pin_5 |PORT_Pin_6 |PORT_Pin_7 |
                                 PORT_Pin_8 |PORT_Pin_9 |PORT_Pin_10|PORT_Pin_11|
                                 PORT_Pin_12|PORT_Pin_13|PORT_Pin_14|PORT_Pin_15|PORT_Pin_16 );
 PORT_InitStructure.PORT_SFUNC = PORT_SFUNC_2;
 PORT_InitStructure.PORT_SANALOG = PORT_SANALOG_DIGITAL;
 PORT_InitStructure.PORT_SPWR = PORT_SPWR_10;
 PORT_Init(PORTD, &PORT_InitStructure);

 // CTRL BUS
 // PD19 = nCS, PD23 = nOE, PD24 = nWE

 PORT_InitStructure.PORT_Pin = (PORT_Pin_19|PORT_Pin_23|PORT_Pin_24);
 PORT_InitStructure.PORT_SFUNC = PORT_SFUNC_2;
 PORT_InitStructure.PORT_SANALOG = PORT_SANALOG_DIGITAL;
 PORT_InitStructure.PORT_SPWR = PORT_SPWR_10;
 PORT_Init(PORTD, &PORT_InitStructure);
};

Фрагмент кода 2.

Сначала включается тактирование портов. Затем происходит разблокировка портов - необходимо записать определенное значение в регистры PORTх→KEY. В этом особенность данного МК: без записи ключа настройка портов не сработает. Далее настраиваются необходимые выводы, участвующие в работе шины.

Параметры инициализирующей структуры также несколько отличаются от прочих микроконтроллеров серии 1986. Особенность заключается в том, что параметры, начинающиеся на S, обозначают слово Set. Параметры, начинающиеся с C, обозначают Clear. То есть управление задается масками - Set параметры устанавливают единицы, а параметры Clear их стирают. Например, при одной и той же маске, выражение PORT_SFUNC = 0х2 приведет к установлению 1-го бита в необходимом регистре, а PORT_СFUNC = 0х2 сбросит этот бит.

Настройка шины

После настройки выводов настраиваются параметры самой шины (фрагмент кода 3):

#define RGN_WS_TIME 4

void ExtBus_Init_RGN0_D8(FunctionalState Ecc_EN, uint32_t baseECC)
{
 EBC_RGN_InitTypeDef EBC_RGNx_IS;

 EXT_BUS_CNTR->KEY = UNLOCK_KEY;

 EXT_BUS_CNTR->RGN0_ECCBASE = baseECC; // 0x10030000;
 EXT_BUS_CNTR->RGN0_ECCS |= (3 << 4); // set FIX_SECC and FIX_DECC bit

 EBC_RGNx_StructInit(&EBC_RGNx_IS);

 //EBC_RGNx_IS.RGN_DIVOCLK = RGN_WS_TIME;
 EBC_RGNx_IS.RGN_WS_HOLD = RGN_WS_TIME;
 EBC_RGNx_IS.RGN_WS_SETUP = RGN_WS_TIME;
 EBC_RGNx_IS.RGN_WS_ACTIVE = RGN_WS_TIME;
 EBC_RGNx_IS.RGN_MODE = 2;  //EBC_MODE_8X;
 EBC_RGNx_IS.RGN_ECCEN = Ecc_EN;

 if (Ecc_EN)
 {
  EBC_RGNx_IS.RGN_ECCMODE = ENABLE;
  EBC_RGNx_IS.RGN_READ32 = ENABLE;
 }

 EBC_RGNx_Init(RGN0, &EBC_RGNx_IS);
 EBC_RGNx_Cmd(RGN0, ENABLE);
}

Фрагмент кода 3.

Сначала включается тактирование, затем производится разблокировка.

На внешней шине выделено 8 регионов адресов. Для каждого региона есть возможность задать различные настройки. Работа будет вестись с первым регионом (RGN0), его адреса составляют диапазон 0x1000_0000 - 0x17FF_FFFF. Оставим под данные адреса с 0x1000_0000 по 0x1002_FFFF, а с адреса 0x1003_0000 начнется область хранения значений ECC для этих данных (RGN0_ECCBASE = baseECC).

Для того чтобы в регистрах ECCADR, ECCDATA, ECCECC отображалась информация о зафиксированной одинарной или двойной ошибке, необходимо выставить биты FIX_SECC или FIX_DECC в регистре RGN0_ECCS. В регистре ECCADR будет содержаться адрес последней обнаруженной ошибки, в ECCDATA - считанные данные до исправления, в ECCECC - считанное значение ECC. Значения этих регистров помогают в отладке, позволяют понять, что реально было считано с шины.

Временные параметры должны рассчитываться в зависимости от скорости работы ядра, но здесь значения выбраны случайно (RGN_WS_TIME = 4). Поскольку задача достижения максимальной скорости работы шины не стоит.

Значение RGN_MODE = 2 определяет режим работы - восьмибитная шина данных. Значения этого поля могут быть такими, RGN_MODE:

  • 0 - 32 битная шина данных
  • 1 - 16 битная шина данных
  • 2 - 8 битная шина данных
  • 3 - 64 битная шина данных

Контроль ECC возможен, только если разрешена работа 32-х битными значениями по шине, поэтому параметр RGN_READ32 должен быть включен. Если этот параметр выключен, то при работе по шине значение ECC формироваться не будет.

Пояснения к тесту Key1

По кнопке Key2 тестируется инициализация внешней памяти. Следовательно, необходима процедура, которая приводит эту память к рассогласованному с ECC состоянию. Состоянию, аналогичному тому, что образуется при включении питания внешнего ОЗУ. Ведь если память была один раз проинициализирована по Key2, то последующие запуски Key2 будут работать с уже согласованной памятью, и смысла в тесте Key2 нет. Поэтому перед запуском теста по Key2 необходимо всегда запускать процедуру по Key1.

При нажатии на Key1 временно выключается режим работы шины с ЕСС, а вся память прописывается индексами. Но можно записать ее и нулями и любыми случайными данными, это не важно. Процедуре по Key1 "портит" обе области - область данных и область ECC - процедура FillData_NoECC().

На время исполнения процедуры гаснут все светодиоды, по окончании загораются VD7 (Completed) и VD9 (Память "испорчена").

Пояснения по тесту Key2

При нажатии на Key2 сначала вызывается дважды функция инициализации внешней памяти Fill_Data32_ByInd(RGN0_StartAddr, ECC_NUM_WORDS). Почему дважды - описано вначале статьи, видимо это баг в микроконтроллере. Возможно позднее появится более эффективное решение, чем двойная запись.

Затем запускается функция TestRD_ECC(), которая считывает записанные значения, сверяет данные и проверяет, что счетчик ошибок не изменился (фрагмент кода 4).

uint32_t TestRD_ECC(void)
{
 uint32_t* addr;
 uint32_t i = 0;
 uint32_t rdValue, errCnt;

 uint32_t eccErrStart = EXT_BUS_CNTR->RGN0_ECCS;

 // Read and Check Data
 addr = (uint32_t*)RGN0_StartAddr;
 errCnt = 0;

 for (i = 0; i < ECC_NUM_WORDS; ++i)
 {
   rdValue = *addr++;
   if(rdValue != i)
     errCnt++;
 }

 return (errCnt == 0) && (EXT_BUS_CNTR->RGN0_ECCS == eccErrStart);
}

Фрагмент кода 4.

При запуске теста гаснут все светодиоды, по окончании теста загорается VD7 (Completed). VD8 загорается в случае, если тест TestRD_ECC() прошел успешно.

Пояснения по тесту Key3

По нажатию на кнопку Key3 запускается функция ErrorTest(), в которой сначала записываются одинарная и двойная ошибки. Это происходит в функции Write_Errors(), которая временно отключает ECC. Затем запускаются тесты на каждую ошибку TestRD_Err1() и TestRD_Err2(). Все, что имеет в названии Err1 относится к одинарной ошибке, а Err2 - соответственно к двойной. Эти тесты считывают регион адресов вокруг адреса внесенной ошибки, находят ее и проверяют счетчики ошибок.

После этого ошибки переписываются правильными значениям, и запускается функция TestRD_ECC() - тест чтения памяти, который уже использовался в Key2. Этот тест показывает, что память полностью согласована с ECC и ошибок не содержит. Индикация на светодиодах в тесте по Key3 поэтому такая же, как и в тесте по Key2 (фрагмент кода 5).

uint32_t ErrorTest(void)
{
 uint32_t val;

 // Write Errors
 Write_Errors();

 // Check single error fixed
 if (!TestRD_Err1())
   return 0;

 // Check double error found
 if (!TestRD_Err2())
    return 0;

 // OverWrite Single Error Data - Double Write
 val = ADDR(RGN0_Addr_Err1);
 ADDR(RGN0_Addr_Err1) = val;
 ADDR(RGN0_Addr_Err1) = val;

 // Restore Double Error
 ADDR(RGN0_Addr_Err2) = Err2_ValueSrc;
 ADDR(RGN0_Addr_Err2) = Err2_ValueSrc;

 // Test Read all Memory without RGN0_ECCS increment
    return TestRD_ECC();
}

 // Внесение ошибок инвертированием одного и двух бит в разных словах
 void Write_Errors(void)
 {
   // ECC Off
   ExtBus_Init_RGN0_D8(DISABLE, RGN0_EccAddr);

   // Single Error
   Err1_ValueSrc = ADDR(RGN0_Addr_Err1);
   Err1_Value = Err1_ValueSrc ^ 1;
   ADDR(RGN0_Addr_Err1) = Err1_Value;

   // Double Error
   Err2_ValueSrc = ADDR(RGN0_Addr_Err2);
   Err2_Value = Err2_ValueSrc ^ 3;
   ADDR(RGN0_Addr_Err2) = Err2_Value;

   // Restore ECC ON
   ExtBus_Init_RGN0_D8(ENABLE, RGN0_EccAddr);
}
Фрагмент кода 5.

Реализация отключения ECC с помощью функции ExtBus_Init_RGN0_D8 избыточна, можно использовать что-то более легковесное. Но для сокращения кода примера была оставлена.

Обратите внимание, что перезапись битых слов делается также дважды. При этом одинарную ошибку можно перезаписать считанным словом, поскольку верное значение восстановилось по ЕСС при чтении. Двойную ошибку так исправить нельзя, необходимо знать исходные данные, чтобы восстановить значение.

Тест одинарной ошибки

Тест одинарной ошибки реализован в функции TestRD_Err1(). Адрес ошибки RGN0_Addr_Err1 был выбран рядом с началом области данных, поэтому чтение данных производится, начиная со стартового адреса и количеством ERR_AREA_SIZE чтений. При этом в цикле чтения ошибочных данных не обнаруживается (errCnt == 0), но счетчик ошибок инкрементируется (значение Err1Cnt > 0). Это связано с тем, что одиночная ошибка есть, но значение было восстановлено. Двойные ошибки не обнаруживаются (Err2Cnt == 0), а регистры ECC_ADDR, ECC_DATA, ECC_ECC возвращают ожидаемые данные по ошибке (фрагмент кода 6).

#define RGN0_StartAddr 0x10000000
#define RGN0_Addr_Err1 0x10000010
#define ERR_AREA_SIZE 20

uint32_t TestRD_Err1(void)
{
 uint32_t* addr;
 uint32_t i = 0;
 uint32_t rdValue, errCnt;
 uint32_t eccErrStart;
 uint32_t Err1Cnt, Err2Cnt, eccLogOK;

 // Начальное значение регистра ошибок
 eccErrStart = EXT_BUS_CNTR->RGN0_ECCS;

 // Чтение и проверка данных
 addr = (uint32_t*)RGN0_StartAddr;
 errCnt = 0;
 for (i = 0; i < ERR_AREA_SIZE; ++i)
 {
   rdValue = *addr++;
   if(rdValue != i)
     errCnt++;
  }

 // Проверка регистров локализации ошибки
 LogEccRegs();
 eccLogOK = (regECC_ADDR == RGN0_Addr_Err1)
          && (regECC_DATA == Err1_Value)
          && (GetECC(regECC_ADDR, Err1_ValueSrc) == regECC_ECC);

 // Счетчики ошибок
 Err1Cnt = (EXT_BUS_CNTR->RGN0_ECCS >> 16) - (eccErrStart >> 16);
 Err2Cnt = ((EXT_BUS_CNTR->RGN0_ECCS >> 8) & 0xFF) - ((eccErrStart >> 8) & 0xFF);

 // Результат return (errCnt == 0) && (Err1Cnt < 2) && (Err2Cnt == 0) && eccLogOK;
}

Фрагмент кода 6.

Для вычисления значения ЕСС используется модифицированная функция одного из пользователей (фрагмент кода 7), она выполняется быстрее. Но можно пользоваться и функцией из спецификации.

const unsigned long long H[8] = {
 (unsigned long long) 0x0738C808099264FF,
 (unsigned long long) 0x38C808099264FF07,
 (unsigned long long) 0xC808099264FF0738,
 (unsigned long long) 0x08099264FF0738C8,
 (unsigned long long) 0x099264FF0738C808,
 (unsigned long long) 0x9264FF0738C80809,
 (unsigned long long) 0x64FF0738C8080992,
 (unsigned long long) 0xFF0738C808099264
};

// Модифицированная функция вычисления ЕСС
unsigned int GetECC(unsigned int data, unsigned int adr)
{
 unsigned int* ptr_H;
 int i, j;
 unsigned int res;
 unsigned int ecc;
 unsigned int datai;
 unsigned int adri;

 ecc =0;
 ptr_H = (unsigned int*)(&H);
 for (i=0; i<8; i++)
 {
  datai = *ptr_H;
  ptr_H++;
  adri = *ptr_H;
  ptr_H++;
  datai &= data;
  adri &= adr;
  res = 0;

  for (j=0; j < 32; j++)
  {
   res ^= adri >> j;
   res ^= datai >> j;
  }
  res &= 0x1;
  res <<= i;
  ecc |= res;
 }

 return ecc;
}

 Фрагмент кода 7.

Тест двойной ошибки

Тест двойной ошибки в функции TestRD_Err2() аналогичен предыдущему тесту. Разница лишь в том, что здесь в цикле обнаружится одно неправильное значение при чтении. Ведь двойные ошибки не восстанавливаются, поэтому errCnt == 1. Счетчик двойных ошибок ожидаемо увеличился Err2Cnt > 0. Но увеличился также и счетчик одинарных ошибок Err1Cnt > 0. Регистры ECC_ADDR, ECC_DATA, ECC_ECC снова возвращают ожидаемые данные по ошибке.

Адрес двойной ошибки был выбран вдалеке от адреса одинарной, поэтому необходимо высчитать стартовый адрес для опроса региона с ошибкой. Также необходимо высчитать значение в стартовой ячейке i_offs, чтобы от него инкрементировать значения по i. Но это значение i_offs можно было бы и не высчитывать, а считать напрямую из стартового адреса, так как известно, что в данном адресе ошибки нет, и значение вернется верное (фрагмент кода 8).

#define RGN0_Addr_Err2 0x10010000
#define ERR_AREA_SIZE 20

uint32_t TestRD_Err2(void)
{
 uint32_t* addr;
 uint32_t i = 0;
 uint32_t rdValue, i_offs, errCnt;
 uint32_t eccErrStart;
 uint32_t Err1Cnt, Err2Cnt, eccLogOK;

 // Начальное значение регистра ошибок
 eccErrStart = EXT_BUS_CNTR->RGN0_ECCS;

 // Вычисления стартового адреса чтения региона с ошибкой
 addr = (uint32_t*)RGN0_Addr_Err2 - ERR_AREA_SIZE / 2;

 // Значение данных по данному адресу
 i_offs = (RGN0_Addr_Err2 / 4 - ERR_AREA_SIZE / 2) & 0xFFFFF;
 errCnt = 0;

 // Чтение и проверка данных
 for (i = 0; i < ERR_AREA_SIZE; ++i)
 {
   rdValue = *addr++;
   if(rdValue != (i + i_offs)) errCnt++;
 }

 // Проверка регистров локализации ошибки
 LogEccRegs();
 eccLogOK = (regECC_ADDR == RGN0_Addr_Err2) && (regECC_DATA == Err2_Value) && (GetECC(regECC_ADDR, Err2_ValueSrc) == regECC_ECC);

 // Счетчики ошибок
 Err1Cnt = (EXT_BUS_CNTR->RGN0_ECCS >> 16) - (eccErrStart >> 16);
 Err2Cnt = ((EXT_BUS_CNTR->RGN0_ECCS >> 8) & 0xFF) - ((eccErrStart >> 8) & 0xFF);

 // Результат
 return (errCnt == 1) && (Err1Cnt < 2) && (Err2Cnt < 2) && eccLogOK;
}

Фрагмент кода 8.

Выводы

На данном примере можно ознакомиться с работой внешней шины МК К1986ВЕ8Т в 8-битном режиме и разобраться с работой последовательно организованной ЕСС.


Контактная информация

Сайт:https://support.milandr.ru
E-mail:support@milandr.ru
Телефон: +7 495 221-13-55