Содержание

Работа с Flash памятью 1636РР1У по внешней шине 1986ВЕ91Т (1986ВЕ1, 1901ВЦ1)

Работу с Flash памятью разберем на примере платы расширения, которая подключается в мезонинный разъем на отладочных платах 1986ВЕ91 и 1986ВЕ1Т. На плате расширения установлены четыре микросхемы 1636РР1У с 8-ми разрядными выводами данных, которые сообща образуют 32-х разрядное слово на внешней шине данных подключенного микроконтроллера.

Код проекта, в несколько измененном виде, доступен на GitHub. (Код модифицирован для поддержки работы с памятью через 1986ВЕ91Т, 1986ВЕ1Т и 1901ВЦ1.)

Энергонезависимая память 1636РР1У может программироваться по последовательному порту, либо по параллельному. При подключении в внешней шине микроконтроллера используется параллельный интерфейс. В проекте данной статьи рассмотрим настройку внешней шины микроконтроллера и основные операции с данной Flash памятью - стирание и запись. Стирание и запись будем производить как всей микросхемы целиком, так и по секторам.

Влючение микроконтроллера и памяти представлено на картинке:

Здесь следует обратить внимание на то, что адресные линии микроконтроллера a0 и a1 не подключаются. Это связано с тем, что шина будет работать в 32-битном режиме. Т.е. адреса на шине будут меняться сразу на 4 байта, поэтому младшие два бита не имеют значения и никуда не подключаются.
(Действительно, ведь если первое 32-битное слово лежит по адресу 0, то следующее 32-битное слово будет лежать по адресу 4, т.е. спустя 4 байта от первого. В двоичном исчислении 4 = b100, первые два бита всегда будут нулевыми.)

В микросхемах памяти данные лежат побайтно и должны выбираться по очереди, поэтому адрес с МК а2 подключается к адресу А0 в памяти. Т.е. первый значащий бит адреса а2, будет выбирать следующий байт в 1636РР1У.

a2 .. a19 - шина адреса. Держит последний адрес обращения. Всегда выход, в третье состояние не переходит.

d0 .. d31 - шина данных. Держит данные, если последней операцией была запись. Если чтение - то выводы находятся в третьем состоянии.

nOE - это сигнал разрешения считывания из памяти, nWE - сигнал разрешения записи в память. Активным уровнем nOE и nWE является ноль и эти сигналы всегда работают в противофазе - активно либо считывание, либо запись. Контроллер шины выставляет сигналы nOE и nWE при соответствующей операции.

nCE - сигнал выбора подключенной микросхемы, низким уровнем МК выбирает микросхему с которой будет работать. В данном случае работа происходит параллельно со всеми микросхемами сразу, поэтому сигнал nCE один на все микросхемы памяти. В контроллере внешней шины нет отдельного сигнала nCE, поэтому в качестве nCE как правило используется одна из старших линий данных. В данном случае на nCE подключен адрес А30, поэтому при обращении к памяти необходимо чтобы в этом бите адреса был 0. Ведь активный уровень сигнала nCE это ноль.

Вместо использования вывода РЕ14 в качестве адресной линии А30, можно было бы проинициализировать вывод РЕ14 в функцию порта и программно подать на nCE ноль.

Линии ByteEnable - nBE0, nBE1, nBE2, nBE3

Когда шина работает в 16-ти битном или 32-х битном режиме, то необходим механизм для обращения к отдельным байтам и полусловам. Так, если нам требуется записать один байт, а шина работает 32-битными данными, то вместе с необходимым байтом на шину данных будут выведены все четыре, т.е. запишется 32-битное слово.

Для того, чтобы этого избежать используются сигналы ByteEnable, по одному на каждый байт - (nBE0, nBE1, nBE2, nBE3). Префикс "n" обозначает, что активным уровнем сигнала является ноль. Эти сигналы подключаются к входам nCE микросхем памяти. Таким образом сигналы ByteEnable делают активной только ту микросхему, к которой идет байтное обращение. nBE0 выбирает младший байт, nBE1 - следующий и т.д. В этом случае выводы nBE как-бы выполняют функции неподключенных адресов a0, a1.

Режим Обращение Данные nBE0 nBE1 nBE2 nBE3
32-bit 32-bit D[31:0] 0 0 0 0
16-bit Lo D[15:0] 0 0 1 1
16-bit Hi D[31:15] 1 1 0 0
8-bit Lo_l D[7:0] 0 1 1 1
8-bit Lo_h D[15:8] 1 0 1 1
8-bit Hi_l D[23:16] 1 1 0 1
8-bit Hi_h D[31:24] 1 1 1 0
16-bit 16-bit D[15:0] 0 0 1 1
8-bit Lo D[7:0] 0 1 1 1
8-bit Hi D[15:8] 1 0 1 1

Это стандартное решение и используется многими производителями. Вот, например, документ по работе внешней шины в микроконтроллерах NXP - Using the External Bus Interface (EBI) on the MPC5510

В 1986ВЕ91 выводы PF4,PF5,PF6 используются и линии адреса A4-A6, и как выводы MODE[0..2], определяющие режим загрузки при подаче питания. При запуске примера возникали случаи, что пример не работает. Оказалось, что они связаны с переключателями MODE. Вероятней всего бывает плохой контакт и по этой причине линии адреса отрабатывают неправильно. Стоит передернуть переключатели MODE туда-сюда плотней, и работоспособность примера восстанавливается. Переключатели следует переключать только после выключения питания.

Инициализация выводов и шины

В соответствии с картинкой, настройка выводов GPIO на выполнение функций шины получается следующая:

void ExtBus_InitPins_A20_D32 (void)
{	
  PORT_InitTypeDef PortInit;

  //  Включение тактирования портов
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTA, ENABLE);
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTB, ENABLE);	
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTE, ENABLE);
  RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTF, ENABLE);
	
  //	Инициализация параметров выводов по умолчанию
  PortInit.PORT_MODE  	  = PORT_MODE_DIGITAL;
  PortInit.PORT_PD 	  = PORT_PD_DRIVER;
  PortInit.PORT_PD_SHM 	  = PORT_PD_SHM_OFF;	
  PortInit.PORT_GFEN 	  = PORT_GFEN_OFF;
  PortInit.PORT_PULL_UP   = PORT_PULL_UP_OFF;
  PortInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF;
  PortInit.PORT_OE        = PORT_OE_OUT;  
  PortInit.PORT_FUNC      = PORT_FUNC_MAIN;  

  // Скорость не максимальная, для избежания переколебаний
  PortInit.PORT_SPEED = PORT_SPEED_FAST;

  //	PF2-PF15 => ADDR[2..15]
  PortInit.PORT_Pin   = (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_Init(MDR_PORTF, &PortInit);	
	
  //	PE0-PE3 => ADDR[16..19]
  PortInit.PORT_Pin   = (PORT_Pin_0 | PORT_Pin_1 | PORT_Pin_2 | PORT_Pin_3);
  PORT_Init(MDR_PORTE, &PortInit);

  // PA0-PA15 => DATA[0..15]
  PortInit.PORT_Pin   = PORT_Pin_All;
  PORT_Init(MDR_PORTA, &PortInit);

  // PB0-PB15 => DATA[16..31]
  PortInit.PORT_Pin   = PORT_Pin_All;
  PORT_Init(MDR_PORTB, &PortInit);

  //PC1 => OE, PC2 => WE, PC3-PC6 => BE0 - BE3
  PortInit.PORT_Pin   = (PORT_Pin_1 | PORT_Pin_2 | PORT_Pin_3 | PORT_Pin_4 | PORT_Pin_5 | PORT_Pin_6);
  PORT_Init(MDR_PORTC, &PortInit);	
}

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

Теперь настройка шины:

void ExtBus_InitFlash (void)
{
  EBC_InitTypeDef EBC_InitStruct;

  // Тактирование
  RST_CLK_PCLKcmd(RST_CLK_PCLK_EBC, ENABLE);		
	
  EBC_DeInit();
  EBC_StructInit(&EBC_InitStruct);
  EBC_InitStruct.EBC_Mode       = EBC_MODE_RAM;
  EBC_InitStruct.EBC_WaitState  = EBC_WAIT_STATE_3HCLK;
  EBC_Init(&EBC_InitStruct);
}

Настройка здесь простая - включение тактирования, выбор режима RAM (поскольку мы будем писать в Flash память), выставление задержек в циклах шины.

Функции работы с 1636РР1У

Рассмотрим функции работы с Flash памятью микросхемы 1636РР1У. Поскольку это Flash память, то для записи в нее значений требуется предварительно ее стереть. При этом все ячейки становятся равны значению 0xFF. При записи значений прописываются только необходимые нули.

Некоторые определения, необходимые для функций работы с Flash.

//  Макрос обращения к 32-битному значению по адресу
#define HWREG(x) (*((volatile uint32_t *)(x)))

//  Определение макроса HWEXTBUS  для обращения к внешней памяти
//  Адрес сдвинут поскольку подключен к памяти с вывода А2 (не с А0).
#define EXTBUS_START_ADDR   0xA0000000
#define EXTBUS_ADDR(x)      (EXTBUS_START_ADDR + ((x) << 2))
#define HWEXTBUS(x)         HWREG(EXTBUS_ADDR(x))

//  Количество попыток запуска операции с памятью
#define TRY_START_COUNT	      10

//  Количество опросов бита D6 для подтверждения запуска операции с памятью
#define WAIT_STARTED_CYCLES   100

//  Статус окончания операции с памятью
typedef enum {flashOk, flashFault} FlashStatus;

(Выбор области адресного пространства с адреса 0xA0000000 объяснен здесь - Типы памяти при работе с внешней шиной).

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

void FlashReset(void)
{
  HWEXTBUS(0) = 0xf0f0f0f0;
}

Стирание всей памяти - Chip Erase

Каждый байт в 32-х разрядном слове управляет отдельной микросхемой, поэтому необходимо формировать командную последовательность одновременно для всех 4-х микросхем Flash сразу. Это же относится и к опросу статусных бит, о них будет сказано позже.

Следует учесть, что в каких-то микросхемах из этих 4-х старт может не произойти, или задержаться относительно остальных по каким-то причинам. Поэтому в коде я использую цикл попыток запуска процедуры стирания, на случай если в какой-то микросхеме запуск не произойдет. При отладке он мне помог, но в финале проекта оказалось что случаи неудачного старта либо редки, либо их нет вовсе. А все неудачи были связаны с предыдущими некорректными операциями с памятью.

Код функции стирания всей памяти:

//  Стирание всей памяти
FlashStatus EraseFullFLASH(void)
{
  volatile uint32_t status, status_last;
  FlashStatus result;
  uint32_t i;

  result = flashFault;
  for (i = 0; i < TRY_START_COUNT; ++i)
  {
    //	Командная последовательность EraseFull
    HWEXTBUS(0x555) = 0xAAAAAAAA;
    HWEXTBUS(0x2AA) = 0x55555555;
    HWEXTBUS(0x555) = 0x80808080;
    HWEXTBUS(0x555) = 0xAAAAAAAA;
    HWEXTBUS(0x2AA) = 0x55555555;
    HWEXTBUS(0x555) = 0x10101010;

    // Проверяем старт EraseFull по переключающемуся биту D6
    if (WaitStarted_D6(0, WAIT_STARTED_CYCLES))
    {
      result = flashOk;			
      break;
    }	
    else  // Сброс если старт не начался    
      FlashReset();
  }	

  // Дожидаемся окончания операции стирания по прекращению переключений D6	
  if (result == flashOk)
  {	
    result = WaitProgressBit_D6(0);
    
    // Сброс в случае неудачи
    if (result != flashOk)
      FlashReset();
  }	
	
  return result;
}

Стирание сектора - Sector Erase

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

FlashStatus EraseFLASHSector(uint32_t SectorInd)
{
  // Последовательность стирания сектора	
  HWEXTBUS(0x555) = 0xAAAAAAAA;
  HWEXTBUS(0x2AA) = 0x55555555;
  HWEXTBUS(0x555) = 0x80808080;
  HWEXTBUS(0x555) = 0xAAAAAAAA;
  HWEXTBUS(0x2AA) = 0x55555555;

  // Указание сектора
  HWEXTBUS(SectorInd << 16) = 0x30303030;	

  // Ожидание завершения
  return WaitProgressBit_D6(SectorInd << 16);
}

Функция записи значения - Program

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

// Функция записи значения
FlashStatus WriteFLASH(uint32_t ADR, uint32_t DATA)
{	
  FlashStatus result;	
  uint32_t i;

  result = flashFault;
  for (i = 0; i < TRY_START_COUNT; ++i)
  {
    //	Последовательность записи слова
    HWEXTBUS(0x555) = 0xAAAAAAAA;
    HWEXTBUS(0x2AA) = 0x55555555;
    HWEXTBUS(0x555) = 0xA0A0A0A0;
    
    //	Запись слова
    HWEXTBUS(ADR) = DATA;
    
    //  Проверка начала операции записи
    if (WaitStarted_D6(ADR, WAIT_STARTED_CYCLES))
    {	
      result = flashOk;
      break;
    }	
    else
      FlashReset();
  }

  // Ожидание окончания операции
  if (result == flashOk)
  {	
    result =  WaitStatusBit_D7(ADR, DATA);
    if (result != flashOk)
      FlashReset();	
  }
  
  return result;
}

Опрос статусных бит D6, D7, D5

Статусный бит D6 в процессе выполнения операций внутри микросхемы 1636РР1У меняется при каждой операции считывания, поэтому его удобно использовать для проверки того, что командная последовательность прошла успешно и заданная операция началась. В функции WaitStarted_D6 проверяется, что за заданное количество обращений к адресу, переключение бита D6 началось.

uint32_t WaitStarted_D6(uint32_t ADR, uint32_t waitCycles)
{
  uint32_t i;
  volatile uint32_t status;
  volatile uint32_t status_last;	

  status_last = HWEXTBUS(ADR);

  // Проверка переключения бит статуса D6 и D2	
  for (i = 0; i < waitCycles; ++i)
  {
    status = HWEXTBUS(ADR);
    if (status != status_last)
      return 1;
    else
      status_last = status;
  }
  return 0;
}

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

Если переключение D6 не прекращается, то следует проверить бит таймаута операции - бит D5. Если выставился бит D5, то значит текущая операция не завершилась успешно, время ожидания превышено и следует вызвать команду сброса, чтобы вывести микросхему из сбойного режима - восстановить работу программного автомата.

FlashStatus WaitProgressBit_D6(uint32_t ADR)
{
  volatile uint32_t status, status_last;
  
  while(1)
  {	
    // Проверка переключения бит статуса D6 и D2
    status_last = HWEXTBUS(ADR);
    status = HWEXTBUS(ADR);
    if (status == status_last)
      break;

    //	Проверка таймаута - D5
    if ((status & 0x20202020) == 0x20202020)
    {
      status_last = HWEXTBUS(ADR);
      status = HWEXTBUS(ADR);
      if (status == status_last) 
        break;
      else	
        return flashFault;
    }
  }
  
  return flashOk;	
}

В процессе работы статусный бит D7 возвращает бит, инверсный по отношению к данным записываемым ячейку. Следовательно при операции записи числа мы используем его для диагностики того, что операция записи завершилась и 7-й бит читаемого слова стал равен 7-му биту записываемых данных.

Операция стирания, в отношении к данному биту, равносильна записи значения 0xFF, следовательно при стирании бит D7 возвращается равным 0 во время исполнения и D7 = 1 при завершении. Этим так-же можно было воспользоваться при стирании вместо опроса D6.

FlashStatus WaitStatusBit_D7(uint32_t ADR, uint32_t DATA)
{
  volatile uint32_t status;
  volatile uint32_t status_last;
  volatile uint32_t D6_Stopped = 0;

  while (1)
  {
    // Сравниваем 7-е биты статуса и записываемых данных
    status = HWEXTBUS(ADR);
    if ((status & 0x80808080) == (DATA & 0x80808080))
      break;

    // Проверка таймаута
    if((status & 0x20202020) == 0x20202020)
    {
      status = HWEXTBUS(ADR);
      if((status & 0x80808080) == (DATA & 0x80808080))	
        break;
      else	
        return flashFault;
    }
  }

  return flashOk;
}

… Код содержит некорректную обработку таймаута, ведь если только одна микросхема выставит таймаут цикл не завершится. Необходим более удачный вариант обработки.

Описание тестов

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

При запуске выбранного теста загораются все светодиоды статуса и продолжают гореть пока происходит тест. При окончании теста все светодиоды выключаются и загорается только статус окончания операции. Если горит:

Светодиод LED1_CYCLE несколько отличается от остальных, в простое он:

В тесте на полную память по кнопке Select, (светодиоды статуса горят):

В тесте на сектора по кнопке Up, (светодиоды статуса горят):

Мигание реализовано для понимания, что микроконтроллер не завис и программа исполняется. Поведение светодиодов станет более понятно из приведенного ниже кода.

  //	Включение всех светодиодов
  LedOn(LED1_CYCLE | LED2_OK | LED3_ERAZE_ERR | LED4_PROG_ERR);
		
  // Выполнение теста, task задается кнопками Select или Up
  if (task == testFlashFull)
    res = TestFlashFull();
  else
    res = TestFlashBySect();
  task = noTask;
				
  //  Выключение светодиодов
  LedOff(LED1_CYCLE | LED2_OK | LED3_ERAZE_ERR | LED4_PROG_ERR);

  //  Выставление статуса на светодиод
  switch (res)
  {
    case resEraseFault:
      LedOn(LED3_ERAZE_ERR);
      break;
    case resProgFault:
      LedOn(LED4_PROG_ERR);
      break;
    case resComError:
      LedOn(LED3_ERAZE_ERR | LED4_PROG_ERR);
      break;				
    default:
      LedOn(LED2_OK);
  }	

Поведение LED1_CYCLE в тестах станет понятно из приведенных кусков кода далее.

Тест всей память

Тест стирания - записи - чтения реализован в функции TestFlash_WR_RD(). Тест выполняется два раза, в первом проходе память заполняется значениями индексов, а во втором проходе память заполняется значениями инверсными к индексам. Перед каждым циклом записи - чтения, память предварительно стирается и проверяется равенство всех данных значению 0xFFFF_FFFF. Это значение соответствует стертым данным.

Вот код функции:

#define MEM_SECT_COUNT 	4
#define MEM_SECT_SIZE   0x10000
#define MEM_SIZE        (MEM_SECT_SIZE * 4)

TestResult TestFlashFull(void)
{
  TestResult result;
  uint32_t modeInv;

  // Тест на запись индексов и инверсных индексам значений
  for (modeInv = 0; modeInv <= 1; ++modeInv)
  {
    //	Стирание всей памяти и проверка что все данные равны 0xFFFF_FFFF
    result = FlashEraseAndTest();
    if (result != resOk)
      break;

    //	Тест всего диапазона адресов, Addr: 0 - 0x40000
    result = TestFlash_WR_RD(0, MEM_SIZE, modeInv);
    if (result != resOk)
      break;	
  }	

  return result;
}	

В функции стирания и проверки памяти FlashEraseAndTest() используется уже описанная функция EraseFullFLASH(). Для проверки значений ячеек памяти используется функция ReadFLASH(). Код:

TestResult FlashEraseAndTest(void)
{
  uint32_t i;

  if (EraseFullFLASH() != flashOk)
  return resComError;

  //	Check Memory Clear
  for (i = 0; i < MEM_SIZE; i++)
  {
    if (ReadFLASH(i) != 0xFFFFFFFF)
      return resEraseFault;
  }
  
  return resOk;
}

//  Функция чтения памяти
uint32_t ReadFLASH(uint32_t ADR)
{
  return (HWEXTBUS(ADR));
}

Тест записи - чтения выполняется функцией TestFlash_WR_RD(). На вход функция получает стартовый адрес startAddr, с которого будут записываться и считываться значения. Количество ячеек для теста передается во втором параметре - dataCount. В третьем параметре modeInv передается то, какие данные будут писаться - индекс, или инверсное значение индекса. В случае несовпадения записанных данных, функция прерывает свое исполнение и возвращает статус ошибки.

TestResult TestFlash_WR_RD(uint32_t startAddr, uint32_t dataCount, uint32_t modeInv)
{	
  uint32_t i = 0;
  uint32_t rdData;
  uint32_t wrData;
  uint32_t ErrCnt = 0;

  //  Запись слова и считывание
  for (i = 0; i < dataCount; i++)
  {	
    if (modeInv == 0)
      wrData = i;     // Индекс
    else
      wrData = ~i;    // Инверсия значения

    //  Программирование слова
    if (WriteFLASH(startAddr + i, wrData) != flashOk)
      return resComError;

    //  Считывание слова
    rdData = ReadFLASH(startAddr + i);

    //  Проверка правильности записанных данных
    if (rdData != wrData)
      return resProgFault;

    //  Обновление светодиода - индикация выполнения операции
    LedShowCycle(LED_PERIOD_TEST);
  }
	
  return resOk;
}

После теста каждого слова обновляется значение счетчика периода светодиода LED1_CYCLE. Переключение светодиода показывает, что тест выполняется.

Тест по секторам

Тест по секторам выполняется аналогично тесту на всю память, только здесь происходит стирание-запись-чтение по одному сектору. В функции TestFlashBySect() происходит запуск теста для каждого сектора.

#define MEM_SECT_COUNT 	4

TestResult TestFlashBySect(void)
{
  TestResult res = resComError;
  uint32_t sectInd;

  for (sectInd = 0; sectInd < MEM_SECT_COUNT; sectInd++)	
  {
    res = TestFlashSector(sectInd);
    if (res != resOk)
      break;
    }

  return res;
}	

Сам тест для сектора производится в функции TestFlashSector(). Для стирания сектора используется описанная ранее функция EraseFLASHSector(). В остальном используется все тоже самое, что и для теста всей памяти, только здесь размер тестируемой памяти ограничен одним сектором.

#define MEM_SECT_SIZE 	0x10000

TestResult TestFlashSector(uint32_t sectorInd)
{
  uint32_t i;

// Тест с данными = i
  LedSwitch(LED1_CYCLE);	

  //  Стирание сектора
  if (EraseFLASHSector(sectorInd) != flashOk)
    return resComError;

  //  Проверка что данные стерлись
  for(i = 0; i < MEM_SECT_SIZE; i++ )
    if(ReadFLASH(MEM_SECT_SIZE * sectorInd + i) != 0xFFFFFFFF)
      return resEraseFault;	

  //  Запись значений
  for(i = 0; i < MEM_SECT_SIZE; i++ )	
    if ((WriteFLASH(MEM_SECT_SIZE * sectorInd + i, i)) != flashOk)
      return resComError;

  //  Чтение и проверка
  for(i = 0; i < MEM_SECT_SIZE; i++ )
    if (ReadFLASH(MEM_SECT_SIZE * sectorInd + i) != i)
      return resProgFault;
		
// Тест с данными = ~i
  LedSwitch(LED1_CYCLE);		

  //  Стирание сектора
  if (EraseFLASHSector(sectorInd) != flashOk)
    return resComError;

  //  Проверка что данные стерлись
  for(i = 0; i < MEM_SECT_SIZE; i++ )
    if (ReadFLASH(MEM_SECT_SIZE * sectorInd + i) != 0xFFFFFFFF)
      return resEraseFault;	

  //  Запись значений
  for(i = 0; i < MEM_SECT_SIZE; i++ )	
    if ((WriteFLASH(MEM_SECT_SIZE * sectorInd + i, ~i)) != flashOk)
      return resComError;

  //  Чтение и проверка
  for (i = 0; i < MEM_SECT_SIZE; i++ )
    if (ReadFLASH(MEM_SECT_SIZE * sectorInd + i) != ~i)
      return resProgFault;

  return resOk;	
}

Итого

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

Модификация для 1986ВЕ1Т

В проект на GitHub внесены изменения для запуска этого же проекта для 1986ВЕ1Т. На картинке показаны перемычки, которые необходимо выставить на отладочной плате для работы с внешней шиной.

1986ve1t_bus_a20_d32.jpg

Стоит отметить, что контакт в мезонинном разъеме бывает недостаточно хорошим. По этой причине, одна из плат расширения с 1636РР1У давала сбои по линиям D0-D2, которые совмещены с линиями MODE[0..2]. Эти линии MODE подтянуты к "0" резисторами и при плохом контакте перетягивали в "0" биты стертой ячейки памяти которые должны быть "1". Данные в таком случае считывались равными 0xFFFFFFF8, см. макроопределение USE_VE1_BRD_FIX в функции FlashEraseAndTest(). Двойное считывание ячейки иногда помогало, поэтому в проекте оставлено макроопределение USE_VE1_BRD_FIX, выключенное по умолчанию. Такие сбои фиксировались 2-3 раза за проверку всех ячеек памяти. Не часто, но алгоритм выходил с ошибкой. Много времени ушло на то, чтобы понять причину происходящего.

Поэтому если проект не работает проверьте контакты.

Модификация для 1901ВЦ1Т

В проект внесена поддержка для 1901ВЦ1Т. При запуске проекта есть две проблемы:

1. Выводы на светодиоды и кнопки используются внешней шиной

Чтобы не усложнят проект переключением выводов на разные функции, кнопки и светодиоды просто отключены. Но при этом нечем запустить тест и негде смотреть статус, поэтому работу примера можно посмотреть только под отладчиком. Как это сделать показано на картинке:

Пояснения:

  1. В режиме отладчика ставим точку останова на проверку значения task и по клику правой мышки на переменной task выбираем в выпадающем меню "Add 'task to …" - "Watch1"
  2. В открывшемся окне Watch1 меняем значение переменной на
    1. 1 - Запуск теста памяти со стиранием памяти целиком
    2. 2 - Запуск теста памяти со стиранием по страницам
  3. Нажимаем F5 (Run) и дожидаемся пока тест выполнится и снова остановится на точке останова. Наводим мышку на переменную res и в подсказке наблюдаем статус выполненного теста. Можно переменную res также вывести в Watch1 и смотреть там.

2. Сигнал nOE в разъеме находится на другом выводе

Плата с 1636РР1У принимает сигнал nOE c пина 11 разъема X33.2 (по 4-й версии платы 1901ВЦ1Т). Но на плате 1901ВЦ1Т на этот пин 11 выведен сигнал c PC9, а сигнал nOE подключен на 27-й пин разъема. Чтобы сигнал nOE дошел до платы 1636РР1 необходимо кинуть проводник от 27-го пина (nOE) к 11-му пину (PC9). При этом важно чтобы сам вывод микроконтроллера PC9 не использовался в программе и оставался в 3-м состоянии.

Pin разъема X33.2 Вывод МК Cигнал c МК Необходимый сигнал для RR1
11 P9 P9 nOE
27 PC1 nOE не используется

Кроме этого, на плате необходимо установить перемычки:

Эти перемычки участвуют в подключении сигналов адреса. Подробности можно посмотреть в файле схемотехники отладочной платы 1901ВЦ1Т.

  1. 1901vc1_1636rr1.jpg

В целом, проект заработал и тест памяти проходит успешно.