======Работа с 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}} В целом, проект заработал и тест памяти проходит успешно.