======Работа с Flash памятью 1636РР1У по внешней шине 1986ВЕ91Т (1986ВЕ1, 1901ВЦ1)======
Работу с Flash памятью разберем на примере платы расширения, которая подключается в мезонинный разъем на отладочных платах 1986ВЕ91 и 1986ВЕ1Т. На плате расширения установлены четыре микросхемы 1636РР1У с 8-ми разрядными выводами данных, которые сообща образуют 32-х разрядное слово на внешней шине данных подключенного микроконтроллера.
Код проекта, в несколько измененном виде, доступен на [[https://github.com/StartMilandr/Examples/tree/master/ExtBus32_1636RR1|GitHub]]. //(Код модифицирован для поддержки работы с памятью через 1986ВЕ91Т, 1986ВЕ1Т и 1901ВЦ1.)//
{{prog:extbus:ve91_rr1.png}}
Энергонезависимая память 1636РР1У может программироваться по последовательному порту, либо по параллельному. При подключении в внешней шине микроконтроллера используется параллельный интерфейс. В проекте данной статьи рассмотрим настройку внешней шины микроконтроллера и основные операции с данной Flash памятью - стирание и запись. Стирание и запись будем производить как всей микросхемы целиком, так и по секторам.
Влючение микроконтроллера и памяти представлено на картинке:
{{prog:extbus:ve91_rr1_scheme.png}}
Здесь следует обратить внимание на то, что адресные линии микроконтроллера **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 - [[https://www.nxp.com/docs/en/application-note/AN3773.pdf|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 объяснен здесь - [[prog:extbus:extbus_memattr|Типы памяти при работе с внешней шиной]])//.
В случае какого-либо сбоя при работе с памятью, необходимо сбросить программный автомат в микросхеме памяти. Вот код сброса:
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;
}
... Код содержит некорректную обработку таймаута, ведь если только одна микросхема выставит таймаут цикл не завершится. Необходим более удачный вариант обработки.
=====Описание тестов=====
Для запуска тестов используется две кнопки на демо-плате:
* Select - запускает тест на //стирание-запись-чтение// всей памяти единым блоком.
* Up - запускает //стирание-запись-чтение// памяти по секторам.
При запуске выбранного теста загораются все светодиоды статуса и продолжают гореть пока происходит тест. При окончании теста все светодиоды выключаются и загорается только статус окончания операции. Если горит:
* LED2_OK - Тест завершился успешно
* LED3_ERAZE_ERR - Ошибка дынных при стирании памяти
* LED4_PROG_ERR - Ошибка данных при записи памяти
* LED3_ERAZE_ERR и LED3_ERAZE_ERR - Произошел сбой в операциях работы в Flash памятью
Светодиод LED1_CYCLE несколько отличается от остальных, в простое он:
* Мигает медленно //(~1сек)// - Тест окончен //(светящийся светодиод отображают статус)// или еще не был запущен.
В тесте на полную память по кнопке Select, //(светодиоды статуса горят)//:
* Горит - Происходит стирание Flash
* Мигает быстро - Идет тест //записи-чтения//.
В тесте на сектора по кнопке 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Т. На картинке показаны перемычки, которые необходимо выставить на отладочной плате для работы с внешней шиной.
{{prog:extbus: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. Выводы на светодиоды и кнопки используются внешней шиной====
Чтобы не усложнят проект переключением выводов на разные функции, кнопки и светодиоды просто отключены. Но при этом нечем запустить тест и негде смотреть статус, поэтому работу примера можно посмотреть только под отладчиком. Как это сделать показано на картинке:
{{prog:extbus:rr1_vc1_test.png}}
Пояснения:
- В режиме отладчика ставим точку останова на проверку значения task и по клику правой мышки на переменной task выбираем в выпадающем меню //"Add 'task to ..."// - //"Watch1"//
- В открывшемся окне //Watch1// меняем значение переменной на
- 1 - Запуск теста памяти со стиранием памяти целиком
- 2 - Запуск теста памяти со стиранием по страницам
- Нажимаем 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** | не используется |
Кроме этого, на плате необходимо установить перемычки:
* DAC1_OUT
* DAC1_REF
* COMP_IN1
Эти перемычки участвуют в подключении сигналов адреса. Подробности можно посмотреть в файле схемотехники отладочной платы 1901ВЦ1Т.
- {{prog:extbus:1901vc1_1636rr1.jpg}}
В целом, проект заработал и тест памяти проходит успешно.