Приветствую. Разбираюсь с работой SPI на stm32g474.
DMA и прерывания пока не использую, т.к. задача разобраться в сути функционала.
Инициализация CR:
Спойлер
Код:
SPI->CR1 =
// Mode: SPI_MODE_MASTER
SPI_CR1_MSTR | SPI_CR1_SSI |
// NSS: SPI_NSS_SOFT
SPI_CR1_SSM |
// BaudRatePrescaler: SPI_BAUDRATEPRESCALER_8
SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0;
/* Configure : NSS management, TI Mode */
SPI->CR2 = SPI_CR2_FRXTH;
С отправкой данных вопросов нет. А вот с получением - есть непонятки. В RM очень много слов про то, когда снимаются/устанавливаются те или иные флаги, но нет рекомендованного алгоритма передачи данных. Поэтому обращаюсь сюда за рекомендациями из опыта. Либо за тем, чтобы показали, что я упустил из RM.
На примере работы с flash-памятью W25Q64FVSSIG. Есть команда GetId (0x9f), ответ на которую д.б.: 0xef4017. Я проверил осцилографом, команда чётко отправляется и микросхема так же чётко отвечает. Причём, что именно она отвечает, я вручную расшифровал ещё до того, как узнал, какой ответ правильный. Таким образом, аппаратный уровень проверен и сомнений не вызывает.
Вопрос1: Как правильно читать RX.
В HAL для этого чипа я увидел вот такой сложный код (собственный пересказ с сохранением сути):
Спойлер
Код:
void xfer(const uint8_t *dtx, uint8_t *drx, size_t sz) {
if (sz > 1)
CLEAR_BIT(SPI->CR2, SPI_CR2_FRXTH);
while (sz > 0) {
while (!(SPI->SR & SPI_SR_TXE)) asm("");
if (sz > 1) {
SPI->DR = *reinterpret_cast<const uint16_t *>(dtx);
dtx+=2;
}
else {
*reinterpret_cast<volatile uint8_t *>(&SPI->DR) = *dtx;
dtx++;
}
while (!(SPI->SR & SPI_SR_RXNE)) asm("");
if (sz > 1) {
*reinterpret_cast<uint16_t *>(drx) = *reinterpret_cast<volatile uint16_t *>(&SPI->DR);
drx+=2;
sz-=2;
if (sz == 1)
SET_BIT(SPI->CR2, SPI_CR2_FRXTH);
}
else {
*drx = *reinterpret_cast<volatile uint8_t *>(&SPI->DR);
drx++;
sz--;
}
}
}
Т.е. мы постоянно прыгаем между чтениями по 1 и 2 байта. При этом в HAL уверены, что получат строго 1/2 байта, хотя в RM написано: "заполнено на четверть/половину или более". Для выполнения команды применяем код:
Спойлер
Код:
const uint8_t tx[4] = {0x9f};
uint8_t rx[4];
spi::xfer(tx, rx, sizeof(rx));
uint32_t id = (rx[1] << 16) | (rx[2] << 8) | rx[3];
Т.е. tx и rx обязательно одинаковые, и в rx мы пропускаем первый байт при обработке ответа. Код, вроде б, работает.
Однако в том же HAL есть отдельная функция HAL_SPI_Transmit, в которой только запись в SPI->DR, а rx стравливается, что-то вроде:
Спойлер
Код:
while (SPI->SR & SPI_SR_FRLVL) {
volatile auto v = SPI->DR;
(void)v;
}
И я изначально пытался разделять отправку/чтение:
Спойлер
Код:
void send1(const uint8_t *dtx, size_t sz) { // пробовал две разные реализации send()
while (sz > 0) {
while (!(SPI->SR & SPI_SR_TXE)) asm("");
if (sz > 1) {
SPI->DR = *reinterpret_cast<const uint16_t *>(dtx);
dtx+=2;
sz-=2;
}
else {
*reinterpret_cast<volatile uint8_t *>(&SPI->DR) = *dtx;
dtx++;
sz--;
}
while (SPI->SR & SPI_SR_FRLVL) {
volatile auto v = SPI->DR;
(void)v;
}
}
}
void send(const uint8_t *dtx, size_t sz) {
uint8_t drx[sz];
bzero(drx, sz);
xfer(dtx, drx, sz);
}
const uint8_t tx[] = {0x9f};
spi::send(tx, sizeof(tx));
uint8_t tx0[3] = {0}, rx[3];
spi::xfer(tx0, rx, sizeof(rx));
Но получал в результате совершенно рандомные результаты: байты целые, но последовательность постоянно куда-то сдвигалась. Я постоянно получал хвост от предыдущего запроса разной длины, байты терялись.
Мне сейчас не "эффективно" надо, а разобраться в сути, что именно делаю не так и как надо. Т.е. хочу понять логику применения, которую ST вкладывал в предоставляемые инструменты доступа.
Суть проблемы, в которой разбираюсь: самая верхняя реализация xfer(), получается, будет работать стабильно до первой рассинхры работы с TxFIFO/RxFIFO.
Отсюда
Вопрос 2: Есть ли какой-то корректный (похожий не на костыль, а на некую рекомендацию) способ сбрасывать RX перед отправкой, чтобы последовательность не нарушалась?
Вопрос 3: А какие вообще гарантии, что при обнаружении флага SPI_SR_RXNE я получаю через DR именно два байта (SPI_CR2_FRXTH=0) или один (SPI_CR2_FRXTH=1), а не три или четыре. Этот регистр объявлен как uint32_t, а в RM этот регистр обозначен как 16-батный. RxFIFO вроде как 4 байта (не помню, где это я прочитал). RM пишет, что да, там может быть и больше. Ориентироваться на SPI_SR_FRLVL при чтении DR?
Вопрос 4: При чтении DR весь RxFIFO передаётся/сбрасывается или только его часть? Это я почему-то в RM не нашёл.
Эти три вопроса можно объединить:
в RM много диаграмм передачи. Но из них я так и не понял цепочку: "линия" - "RX-буфер" - "чтение DR". Нет понимания механизма, что оказывается в RX в процессе передачи, и что я получу через DR на каждом из этапом при выполнении команды.