Работу с 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 ноль.
Когда шина работает в 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 память), выставление задержек в циклах шины.
Рассмотрим функции работы с 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; }
Каждый байт в 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; }
После того, как на функции 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); }
Для записи числа необходимо сформировать командную последовательность записи для каждой микросхемы, а затем сделать запись значения в заданный адрес. Цикл по 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 в процессе выполнения операций внутри микросхемы 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 памятью по параллельной шине. Надеюсь, что для начального ознакомления с данной темой этого достаточно. Пример же можно модифицировать и реализовать различные варианты работы с памятью, ведь опираясь на какой-то функционал сделать это всегда легче, чем с чистого листа.
В проект на GitHub внесены изменения для запуска этого же проекта для 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Т. При запуске проекта есть две проблемы:
Чтобы не усложнят проект переключением выводов на разные функции, кнопки и светодиоды просто отключены. Но при этом нечем запустить тест и негде смотреть статус, поэтому работу примера можно посмотреть только под отладчиком. Как это сделать показано на картинке:
Пояснения:
Плата с 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Т.
В целом, проект заработал и тест памяти проходит успешно.