‮Сдвиг по фазе (kincajou) wrote,
‮Сдвиг по фазе
kincajou

worklog: STM32 работа с QuadSPI-флешкой через DMA.

В общем, всё работает, но есть нюансы.

Т.к. нигде я не нашёл достаточно детальных гайдов, а как работает макаронный монстр из STM32F7xx_HAL_Driver я не понимаю, то пришлось всё делать самому. Заодно изучил несколько вкусных штук. И, как водится, результаты экспериментов отличаются от чистой теории.

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

Потом, разумеется, начались сложности :)

Изначально я хотел добиться полноценной работы в настоящем Quad-режиме, чтобы получить максимально возможную производительность аппаратной части. 4-битная шина на 108 мегагерц даёт теоретический предел в 432 мегабита, без учета оверхеда. Используемый мной в настоящее время чип N25Q128 несколько лукавит: 108 мегагерц доступны для команд, работающих с внутренним буфером и регистрами, сам же массив флэш-памяти можно разогнать до 54 мегагерц максимум, именно на этой частоте он способен выдавать данные на чтение (запись, конечно же, ещё медленнее). Но даже так, 4 бита * 54 мегагерца дадут 216 мегабит (27 мегабайт) в секунду, что очень даже недурно для микросхемки с 8 ножками. Разумеется, после учёта всего оверхеда скорость будет ещё ниже, но пусть она даже в два раза снизится - пофиг, это всё равно более чем достаточно для моих задач.

Итак, с чего всё начинается:
1) порт QUADSPI_BK1
2) пуллап 47к на nCE, пуллдаун 47к на CK (даташит рекомендует только пуллап 10к, но без пуллдауна CK как-то странно подёргивается. 47к достаточно, чтобы надёжно прижать его к земле)
3) инициализация модуля QUADSPI тривиальна и подводных камней в ней я не обнаружил. Однако, в процедуру запуска модуля удобно сразу добавить две команды для сброса шины:
QUADSPI->CCR = 0x00000166; // send "Reset Enable" (0x66) in 1-0-0 mode
QUADSPI->CCR = 0x00000199; // send "Reset" (0x99) in 1-0-0 mode

4) теперь самое время сказать чипу флэш-памяти, что мы настроены более чем серьёзно и хотим 4-битный интерфейс. Для этого пишем в регистр Volatile Enhanced Configuration Register, что мы именно это и хотим; при этом не забывая, что для любой записи, будь то в регистр конфига или массив памяти, нужна команда-открывашка Write Enable (WREN):
QSPI_WREN (QIO_SINGLE);
QSPI_write_byte (QIO_WRVECR, QIO_SINGLE, (0<<7)|(1<<6)|(0<<5)|(0<<4)|(1<<3)|0x07);

Эти две функции - самописное нагромождение таких заклинаний:

static void QSPI_WREN (QSPI_QIO_mode_t mode) { /* Write Enable (WREN) instruction via single wire */
//while (QUADSPI->SR & QUADSPI_SR_BUSY) { }; // wait for BUSY flag to be cleared
QUADSPI->CCR =
(0 << QUADSPI_CCR_DDRM_Pos) |
(0 << QUADSPI_CCR_DHHC_Pos) |
(0 << QUADSPI_CCR_SIOO_Pos) | // send instruction for each command (0) or only for 1st command (1)
(0x0 << QUADSPI_CCR_FMODE_Pos) | // Functional mode; 0 : indirect write, 1 : indirect read, 2 : autopolling, 3 : memory-mapped
(QIO_SKIP << QUADSPI_CCR_DMODE_Pos) | // Data mode: 0 : no data, 1 : data on single line, 2: data on dual line, 3 : quad line
(0x0 << QUADSPI_CCR_DCYC_Pos) | // number of dummy cycles, 0..31
(0x0 << QUADSPI_CCR_ABSIZE_Pos) | // alternate byte size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ABMODE_Pos) | // alternate bytes mode; 0: no alt bytes, 1 : on single line, 2 : dual line, 3 : quad line
(0x0 << QUADSPI_CCR_ADSIZE_Pos) | // address size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ADMODE_Pos) | // address mode; 0: no address, 1 : on single line, 2 : dual line, 3 : quad line
((mode & 0x03) << QUADSPI_CCR_IMODE_Pos) | // instrustion mode; 0: no instruction, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_WREN << QUADSPI_CCR_INSTRUCTION_Pos); // instruction code
//while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { };
while (QUADSPI->SR & QUADSPI_SR_BUSY) { }; // wait for BUSY flag to be cleared
}

static void QSPI_write_byte (QSPI_QIO_instruction_t instruction, QSPI_QIO_mode_t mode, uint8_t data) {
//while (QUADSPI->SR & QUADSPI_SR_BUSY) { }; // wait for BUSY flag to be cleared
QUADSPI->CCR =
(0 << QUADSPI_CCR_DDRM_Pos) |
(0 << QUADSPI_CCR_DHHC_Pos) |
(0 << QUADSPI_CCR_SIOO_Pos) | // send instruction for each command (0) or only for 1st command (1)
(0x0 << QUADSPI_CCR_FMODE_Pos) | // Functional mode; 0 : indirect write, 1 : indirect read, 2 : autopolling, 3 : memory-mapped
((mode & 0x03) << QUADSPI_CCR_DMODE_Pos) | // Data mode: 0 : no data, 1 : data on single line, 2: data on dual line, 3 : quad line
(0x0 << QUADSPI_CCR_DCYC_Pos) | // number of dummy cycles, 0..31
(0x0 << QUADSPI_CCR_ABSIZE_Pos) | // alternate byte size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(0x0 << QUADSPI_CCR_ABMODE_Pos) | // alternate bytes mode; 0: no alt bytes, 1 : on single line, 2 : dual line, 3 : quad line
(0x0 << QUADSPI_CCR_ADSIZE_Pos) | // address size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(0x0 << QUADSPI_CCR_ADMODE_Pos) | // address mode; 0: no address, 1 : on single line, 2 : dual line, 3 : quad line
((mode & 0x03) << QUADSPI_CCR_IMODE_Pos) | // instrustion mode mode; 0: no instruction, 1 : on single line, 2 : dual line, 3 : quad line
((instruction & 0xFF) << QUADSPI_CCR_INSTRUCTION_Pos); // instruction code
while (!(QUADSPI->SR & QUADSPI_SR_FTF)) { }; // wait for FTF (FIFO threshold level should be configured to 0 at this moment)
QUADSPI->DLR = 0;
QUADSPI->DR = data; // VECR<7> = 0, VECR<6:0> = 1
while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { };
//QUADSPI->FCR = QUADSPI_FCR_CTCF;
while (QUADSPI->SR & QUADSPI_SR_BUSY) { }; // wait for BUSY flag to be cleared
}


Тут всё тривиально: модуль QUADSPI программируется на выдачу инструкции по одному проводу, код инструкции сначала WREN, затем WRVECR (у этой инструкции есть однобайтовый аргумент - собственно, значение VECR. Я его прописал в такой дурацкой форме лишь для простоты.

После этого этапа чип готов к полноскоростной работе по 4-битной шине.

Теперь самое вкусное: чтение и запись массивов данных при помощи DMA. Я не ставил себе цель добиться максимальной ОПТИМАЛЬНОСТИ кода - поигравшись с параметрами гранулярности можно, наверное, выжать сколько-то процентов сверху - мне нужно было, чтобы он вообще заработал.

Итак, функция чтения как есть - без прикрас:
void QSPI_fast_read (uint32_t addr, size_t count, uint8_t *dst) {
/* Quad Command Fast Read (QCFR), для инструкции, адреса и данных используются 4 линии
* Адрес: 24-битный (3 байта - см. QUADSPI_CCR_ABSIZ)
* Чтение: побайтовое (?)
* Пустышки: по-умолчанию 15 (позволяет гарантированно работать на максимальной частоте шины, может быть оптимизировано)
*
* Чтение через DMA, FIFO threshold = 1 (4?)
* DMA2, Stream 7, Channel 3
*/
DMA_Stream_TypeDef *stream = DMA2_Stream7;
//if (!gFlags.QSPI.Mode) QSPI_prepare_Quad_Mode();

/* prepare DMA for data transmission phase */
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; /* enable DMA2 if it is not enabled yet */
stream->CR = 0;
stream->CR =
(0x03 << DMA_SxCR_CHSEL_Pos) | // channel id
(0x0 << DMA_SxCR_MBURST_Pos) | // MemBurst 0x0: single transfer, 0x1: burst of 4 beats, 0x2: burst of 8; 0x3: burst of 16 beats
(0x0 << DMA_SxCR_PBURST_Pos) | // PeriphBurst 0x0: single transfer, 0x1: burst of 4 beats, 0x2: burst of 8; 0x3: burst of 16 beats
(0x03 << DMA_SxCR_PL_Pos) | // priority level (0 lowest; 3 highest)
(0x00 << DMA_SxCR_MSIZE_Pos) | // 8 bit source memory
(0x00 << DMA_SxCR_PSIZE_Pos) | // 8-bit target register
(1 * DMA_SxCR_MINC) | // memory increment
(0 * DMA_SxCR_PINC) | // no periph increment
(0x00 << DMA_SxCR_DIR_Pos ) | // direction 0: P->M, 1: M->P
(0 * DMA_SxCR_PFCTRL) | // 0 - DMA is the flow controller, 1 - periph is the flow controller
(0 * DMA_SxCR_EN)
;
stream->M0AR = (uint32_t)dst; /* mem address */
stream->PAR = (uint32_t)&QUADSPI->DR; /* periph address */
stream->NDTR = count;

QUADSPI->FCR = -1; // clear all flags
QUADSPI->DLR = (count-1);
QUADSPI->CCR =
(0 << QUADSPI_CCR_DDRM_Pos) |
(0 << QUADSPI_CCR_DHHC_Pos) |
(0 << QUADSPI_CCR_SIOO_Pos) | // send instruction for each command (0) or only for 1st command (1)
(0x1 << QUADSPI_CCR_FMODE_Pos) | // Functional mode; 0 : indirect write, 1 : indirect read, 2 : autopolling, 3 : memory-mapped
(QIO_QUAD << QUADSPI_CCR_DMODE_Pos) | // Data mode: 0 : no data, 1 : data on single line, 2: data on dual line, 3 : quad line
(10 << QUADSPI_CCR_DCYC_Pos) | // number of dummy cycles, 0..31
(0x0 << QUADSPI_CCR_ABSIZE_Pos) | // alternate byte size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ABMODE_Pos) | // alternate bytes mode; 0: no alt bytes, 1 : on single line, 2 : dual line, 3 : quad line
(0x2 << QUADSPI_CCR_ADSIZE_Pos) | // address size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_QUAD << QUADSPI_CCR_ADMODE_Pos) | // address mode; 0: no address, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_QUAD << QUADSPI_CCR_IMODE_Pos) | // instrustion mode mode; 0: no instruction, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_QCFR << QUADSPI_CCR_INSTRUCTION_Pos); // instruction code
QUADSPI->CR |= QUADSPI_CR_DMAEN; // разрешить работу через DMA
stream->CR |= DMA_SxCR_EN;
QUADSPI->AR = addr; // Запись в address register при (ADMODE!=0)&&(FMODE=1) инициирует транзакцию (DMA уже включен)
while (!(DMA2->HISR & DMA_HISR_TCIF7)) {}; // дожидаемся окончания работы DMA
DMA2->HIFCR |= DMA_HIFCR_CTCIF7;
stream->CR &= ~DMA_SxCR_EN;
SCB_InvalidateDCache_by_Addr ((uint32_t*)dst, count); // сбрасываем кэш

QUADSPI->CR &= ~QUADSPI_CR_DMAEN; // выключить работу через DMA
}


Тут есть подводные камни, целых два с половиной. Во-первых, важен порядок операций. Модуль QUADSPI так устроен, что у него нет какой-то специальной дёргалки для инициации процесса обмена данными, он начинает работать автоматически при достижении сочетания определённых условий. Это сочетание зависит от выбранного режима работы! Поэтому включать DMA надо аккурат перед записью адреса, а эта запись адреса должна быть после установки регистра CCR. Если всё сделать так, что QUADSPI понимает, что от него хотят чтение данных, он дёргает заранее проинизиализированный DMA и понеслась. Тут я ловлю окончание процесса передачи самым тупым способом -- смотрю на флажок -- но в принципе ничего не мешает вынести сие в обработку прерывания и заниматься какими-то более важными вещами, пока идёт обмен данными. Разумеется, после завершения транзакции нужно обновить кэш, иначе данные могут просто буквально потерятся.

Второй подводный камень: количество пересылаемых данных надо прописать и в QUADSPI, и в DMA, причём семантика немного разная: QUADSPI отсчитывает количество "от нуля" (т.е. значение 0 это для него "один байт"), а DMA устроен чуть более по-человечески, поэтому для него 1 байт это 1 байт. Именно из-за этого такая операция -- QUADSPI->DLR = (count-1);

Половина подводного камня: читать из флэши можно сколько угодно байт, начиная с любого адреса, никаких boundary тут нет (за исключением выхода за границу чипа, но щас я этим не заморачиваюсь). Закавыка тут в том, что операция чтения получается устроена несимметрично относительно операции чтения, о которой ниже:

void QSPI_page_program (uint32_t addr, size_t count, const uint8_t *src) {
/* N25Q128: Размер страницы (page) = 256 байт; 65536 страниц всего (у других чипов может быть иначе)/
* Инструкция Quad Command Page Program (QCPP)
* Адрес: 24-битный (3 байта - см. QUADSPI_CCR_ABSIZ). Нюанс:
* If the 8 least significant address bits (A7-A0) are not all zero, all transmitted data that
* go beyond the end of the current page are programmed from the start address of the same page
* (from the address whose 8 least significant bits (A7-A0) are all zero).
* Поэтому адрес желательно выравнивать по границе 256 байт (для файловых систем типа FAT это выполняется автоматически)
* Запись: побайтовая
* Пустышки: по-умолчанию 15 (позволяет гарантированно работать на максимальной частоте шины, может быть оптимизировано)
*
* Количество данных:
* If more than 256 bytes are sent to the device, previously latched data are discarded and the
* last 256 data bytes are guaranteed to be programmed correctly within the same page. If less
* than 256 data bytes are sent to device, they are correctly programmed at the requested
* addresses without having any effects on the other bytes of the same page.
*
* Запись через DMA, FIFO threshold = 1 (4?)
* DMA2, Stream 7, Channel 3
*/
DMA_Stream_TypeDef *stream = DMA2_Stream7;

//if (!gFlags.QSPI.Mode) QSPI_prepare_Quad_Mode();

QSPI_WREN(QIO_QUAD); // разрешаем запись

/* prepare DMA for data transmission phase */
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; /* enable DMA2 if it is not enabled yet */
stream->CR = 0;
stream->CR =
(0x03 << DMA_SxCR_CHSEL_Pos) | // channel id
(0x0 << DMA_SxCR_MBURST_Pos) | // MemBurst 0x0: single transfer, 0x1: burst of 4 beats, 0x2: burst of 8; 0x3: burst of 16 beats
(0x0 << DMA_SxCR_PBURST_Pos) | // PeriphBurst 0x0: single transfer, 0x1: burst of 4 beats, 0x2: burst of 8; 0x3: burst of 16 beats
(0x03 << DMA_SxCR_PL_Pos) | // priority level (0 lowest; 3 highest)
(0x00 << DMA_SxCR_MSIZE_Pos) | // 8 bit memory
(0x00 << DMA_SxCR_PSIZE_Pos) | // 8-bit register
(1 * DMA_SxCR_MINC) | // memory increment
(0 * DMA_SxCR_PINC) | // periph increment
(0x01 << DMA_SxCR_DIR_Pos ) | // direction 0: P->M, 1: M->P
(0 * DMA_SxCR_PFCTRL) | // 0 - DMA is the flow controller, 1 - periph is the flow controller
(0 * DMA_SxCR_EN)
;
stream->M0AR = (uint32_t)src; /* mem address */
stream->PAR = (uint32_t)&QUADSPI->DR; /* periph address */
stream->NDTR = count;

QUADSPI->FCR = -1; // clear all flags
QUADSPI->DLR = (count-1);
QUADSPI->CCR =
(0 << QUADSPI_CCR_DDRM_Pos) |
(0 << QUADSPI_CCR_DHHC_Pos) |
(0 << QUADSPI_CCR_SIOO_Pos) | // send instruction for each command (0) or only for 1st command (1)
(0x0 << QUADSPI_CCR_FMODE_Pos) | // Functional mode; 0 : indirect write, 1 : indirect read, 2 : autopolling, 3 : memory-mapped
(QIO_QUAD << QUADSPI_CCR_DMODE_Pos) | // Data mode: 0 : no data, 1 : data on single line, 2: data on dual line, 3 : quad line
(0 << QUADSPI_CCR_DCYC_Pos) | // number of dummy cycles, 0..31
(0x0 << QUADSPI_CCR_ABSIZE_Pos) | // alternate byte size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ABMODE_Pos) | // alternate bytes mode; 0: no alt bytes, 1 : on single line, 2 : dual line, 3 : quad line
(0x2 << QUADSPI_CCR_ADSIZE_Pos) | // address size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_QUAD << QUADSPI_CCR_ADMODE_Pos) | // address mode; 0: no address, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_QUAD << QUADSPI_CCR_IMODE_Pos) | // instrustion mode mode; 0: no instruction, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_QCPP << QUADSPI_CCR_INSTRUCTION_Pos); // instruction code

SCB_CleanDCache_by_Addr ((uint32_t*)src, count); // обновляем кэш перед передачей
stream->CR |= DMA_SxCR_EN;
QUADSPI->CR |= QUADSPI_CR_DMAEN; // разрешить работу через DMA
QUADSPI->AR = addr;
while (!(DMA2->HISR & DMA_HISR_TCIF7)) {}; // дожидаемся окончания работы DMA
DMA2->HIFCR |= DMA_HIFCR_CTCIF7;
stream->CR &= ~DMA_SxCR_EN;
QUADSPI->CR &= ~QUADSPI_CR_DMAEN; // выключить работу через DMA
}


В общем, всё довольно тривиально, кроме вышеупомянутой особенности порядка операций. Так же не забываем прокачать кэш ДО инициации передачи данных, иначе во флэшку улетит полная чепуха.

Здесь вылезает та самая половина подводного камня во весь свой размер: запись во все известные мне чипы QSPI флжши идёт строго постранично, с заворотом через границу страницы (256 байт). И если начать запись можно с любого адреса, то при выходе объёма за границу страницы мы получаем плохо предсказуемый эффект с уничтожением ранее записанных В БУФЕР! данных. Физическая запись данных в массив памяти начинается после того, как снят строб выбора чипа и продолжается довольно долго.

Операция очистки массива памяти имеет три варианта: полная очистка (и это самая длинная операция из всех, продолжается в среднем 170 секунд, может быть до 250 секунд!!!), посекторная очистка, очистка субсектора. Что это и зачем - разберётесь уже сами, там всё тривиально и скучно.

За ходом процесса записи или очистки можно (и нужно) следить при помощи автоматического поллинга статуса:

void QSPI_poll_status(void) {
//if (!gFlags.QSPI.Mode) QSPI_prepare_Quad_Mode();
QUADSPI->DLR = 0;
QUADSPI->PSMKR = 0x00000001; // polling status mask register : bit 0 is WIP (Write-in-progress)
QUADSPI->PIR = 8; // polling interval register, set it to whatever you want
QUADSPI->CCR =
(0 << QUADSPI_CCR_DDRM_Pos) |
(0 << QUADSPI_CCR_DHHC_Pos) |
(0 << QUADSPI_CCR_SIOO_Pos) | // send instruction for each command (0) or only for 1st command (1)
(0x2 << QUADSPI_CCR_FMODE_Pos) | // Functional mode; 0 : indirect write, 1 : indirect read, 2 : autopolling, 3 : memory-mapped
(QIO_QUAD << QUADSPI_CCR_DMODE_Pos) | // Data mode: 0 : no data, 1 : data on single line, 2: data on dual line, 3 : quad line
(0x0 << QUADSPI_CCR_DCYC_Pos) | // number of dummy cycles, 0..31
(0x0 << QUADSPI_CCR_ABSIZE_Pos) | // alternate byte size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ABMODE_Pos) | // alternate bytes mode; 0: no alt bytes, 1 : on single line, 2 : dual line, 3 : quad line
(0x0 << QUADSPI_CCR_ADSIZE_Pos) | // address size; 0: 8-bit, 1: 16-bit, 2: 24-bit, 3: 32-bit
(QIO_SKIP << QUADSPI_CCR_ADMODE_Pos) | // address mode; 0: no address, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_QUAD << QUADSPI_CCR_IMODE_Pos) | // instrustion mode mode; 0: no instruction, 1 : on single line, 2 : dual line, 3 : quad line
(QIO_RDSR << QUADSPI_CCR_INSTRUCTION_Pos); // instruction code
while (!(QUADSPI->SR & QUADSPI_SR_SMF)) { };
QUADSPI->FCR = QUADSPI_FCR_CSMF;
}


Тут включается третий режим работы модуля QUADSPI, в котором он сам шлёт команду (которую пропишем в CCR, обычно это чтение регистра статуса, но может быть и что-то иное) до тех пор, пока в ответных данных не попадётся нужный бит. Схема очень простая: модуль шлёт команду, читает ответ, накладывает на него маску из регистра QUADSPI->PSMKR и если после этой операции остались ненулевые биты, то проводит с ними либо побитовый AND, либо побитовый OR, в зависимости от состояния бита QUADSPI->CR.PMM (0 это AND, 1 это OR). Если результат операции ненулевой, то считается, что поллинг сработал и выскакивает флажок QUADSPI->SR.SMF; далее тривиально. Так же будет удобно установить бит QUADSPI->CR.APMS в единицу, чтобы автоматический поллинг автоматически бы прекращался после обнаружения факта совпадения.

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

Ну и самое смешное, как водится, на десерт.

Чипы QSPI флэш имеют стандартизированный набор инструкций, совместимый по формату и данным (не знаю, правда, всегда ли они все так совместимы), а так же чётко описанные регистры конфигурации и статуса. Например, есть регистр NVCR - Non Volatile Configuration Register, в нём содержится базовая инфа о настройках чипа: например, о количестве циклов-пустышек, необходимых для перенастройки шины, когда она переключает направление данных. Чипы, которые я использую, пре-программированы на фабрике на самые щадящие и универсальные настройки. Я наивно прочитал их и использовал в коде, но столкнулся с очень странной проблемой: данные читаются, но с целым и фиксированным сдвигом во времени. Причём сдвиг одинаковый вне зависимости от частоты шины - что на 108 мегагерц, что на 400 килогерц, пофигу. Думал-думал, так и не придумал ничего улчше, чем поиграться с настройкой QUADSPI->CCR.DCYC (кол-во пустышек в транзакции). Операция записи идёт без этой фазы, а вот операция чтения требует её наличия - изначально я использовал значение из NVCR (0b1111 = 15) и с этой настройкой данные сбивались. Уменьшил DCYC до 10 и всё внезапно заработало без сбоев, на любой скорости, любой адрес.

Почему так и куда делись "лишние" 5 циклов - понятия не имею!
Tags: радио
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 25 comments