24424

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

Дата последнего изменения: 29.04.2020 11:13:21

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

Рисунок 1. Модуль расширения FLASH-памяти на основе 1636РР1У.

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

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

Рисунок 2. Схема включения микроконтроллера 1986ВЕ91Т и модуля FLASH-памяти 1636РР1У.

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


Если же выводы адреса подключить напрямую (А0-А0, А1-А1 и т.д.) и попытаться читать микроконтроллером с адресами, которые не кратны 4, то байты будут смещаться. Это означает что если читать в 32-разрядную переменную, то в зависимости от адреса 0, 1, 2, 3 младший читаемый байт будет приходить в разные байты в составе этой 32-разрядной переменной

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

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

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

nOE - это сигнал разрешения считывания из памяти;

nWE - сигнал разрешения записи в память.

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

nCE - сигнал выбора подключенной микросхемы.

Активный уровень nCE - низкий. При nCE=0 микроконтроллер выбирает микросхему, с которой будет работать.

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

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

В соответствии с рисунком 2 настройка выводов GPIO на выполнение функций шины следующая (фрагмент кода 1):

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);
}

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

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

Настройка шины приведена в фрагменте кода 2:

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);
}

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

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

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

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

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

// Макрос обращения к 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;

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

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

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

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

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

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

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

Следует учесть, что в каких-то микросхемах из этих 4-х 1636РР1У старт может не произойти или задержаться относительно остальных (при некорректных операциях с памятью). Поэтому в коде используется цикл попыток запуска процедуры стирания на случай, если в какой-то микросхеме запуск не произойдет. Наличие такого цикла может помочь при отладке программы.

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

// Стирание всей памяти
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;
 }

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

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

Функция стирания сектора представлена в фрагменте кода 6.

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);
}

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

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

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

// Функция записи значения
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;
}


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

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

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

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;
}

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

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

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

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;
}

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

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

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

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;
}

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

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

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

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

  • Select - запускает тест на стирание-запись-чтение всей памяти единым блоком.
  • Up - запускает стирание-запись-чтение памяти по секторам.

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

  • LED2_OK - тест завершился успешно;
  • LED3_ERAZE_ERR - ошибка данных при стирании памяти;
  • LED4_PROG_ERR - ошибка данных при записи памяти;
  • LED3_ERAZE_ERR и LED3_ERAZE_ERR - произошел сбой в операциях работы в Flash памятью.

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

  • мигает медленно (~1сек) - тест окончен (светящийся светодиод отображают статус) или еще не был запущен.

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

  • горит - происходит стирание Flash
  • мигает быстро - идет тест записи-чтения.

В тесте стирание-запись-чтение по секторам по кнопке Up (светодиоды статуса горят):

  • переключается при смене сектора и записываемых данных.

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

// Включение всех светодиодов
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);
}

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

Поведение LED1_CYCLE в тестах станет понятно далее.

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

Тест стирания - записи - чтения реализован в функции TestFlash_WR_RD(). Тест выполняется два раза:

  • в первом проходе память заполняется значениями индексов;

  • во втором проходе память заполняется значениями, инверсными к индексам.

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

Код функции представлен в фрагменте кода 12:

#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;
}

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

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

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));
}

фрагмент кода 13.

Тест записи - чтения выполняется функцией TestFlash_WR_RD().

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

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

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;
}

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

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

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

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

#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;
}

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

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

#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;
}

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


Выводы

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

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

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

Рисунок 3. Работа с внешней шине на отладочной плате для микроконтроллера 1986ВЕ1Т.

Стоит отметить, что контакт в мезонинном разъеме бывает недостаточно хорошим. По этой причине плата расширения на основе микросхем памяти 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. Выводы на светодиоды и кнопки используются внешней шиной

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

Рисунок 4.

Последовательность действий:

1.В режиме отладчика поставить точку останова на проверку значения task и по клику правой мышки на переменной task выбрать в выпадающем меню "Add 'task to …" - "Watch1"
2.В открывшемся окне Watch1 изменить значение переменной на:
   1 - Запуск теста памяти со стиранием по страницам;
   2 - Запуск теста памяти со стиранием памяти целиком.
3.Нажать F5 (Run) и дождаться, пока тест выполнится и снова остановится на точке останова
4.Далее навести мышку на переменную 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 не используется

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

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


Рисунок 5.


Примечание:

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


Сохранить статью в PDF

Файлы для скачивания

Программное обеспечение

Standard Peripherals Library + software pack для Keil MDK 5
Standard Peripherals Library + software pack для Keil MDK 5
ОФИЦИАЛЬНАЯ СБОРКА
Standard Peripherals Library – библиотека для микроконтроллеров.

Теги

Была ли статья полезной?