Например TDA7294

Форум РадиоКот • Просмотр темы - stm32g4 - SPI, чтение из DR
Форум РадиоКот
Здесь можно немножко помяукать :)

Текущее время: Пт сен 19, 2025 20:57:14

Часовой пояс: UTC + 3 часа


ПРЯМО СЕЙЧАС:



Начать новую тему Ответить на тему  [ Сообщений: 4 ] 
Автор Сообщение
Не в сети
 Заголовок сообщения: stm32g4 - SPI, чтение из DR
СообщениеДобавлено: Ср авг 27, 2025 10:55:39 
Родился

Зарегистрирован: Ср авг 13, 2025 11:45:44
Сообщений: 10
Рейтинг сообщения: 0
Приветствую. Разбираюсь с работой 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 на каждом из этапом при выполнении команды.


Вернуться наверх
 
Не в сети
 Заголовок сообщения: Re: stm32g4 - SPI, чтение из DR
СообщениеДобавлено: Ср авг 27, 2025 11:26:59 
Прорезались зубы
Аватар пользователя

Карма: 5
Рейтинг сообщений: 30
Зарегистрирован: Пн июл 04, 2016 16:51:22
Сообщений: 230
Откуда: Россия, Омск
Рейтинг сообщения: 0
Для STM32F7xxx (впрочем, аналогично работает и на F4 и на других, имеющих буфер приёма).

Код:
uint8_t SPI_Exchange(uint8_t Data) {
   uint8_t v;
   while (SPI4->SR & (SPI_SR_RXNE | SPI_SR_FRLVL)) {
      v = *(volatile uint8_t *)&(SPI4->DR);
   }
   *(volatile uint8_t *)&(SPI4->DR) = Data;
   while (SPI4->SR & (SPI_SR_FTLVL | SPI_SR_BSY));
   while (!(SPI4->SR & (SPI_SR_RXNE)));
   v = *(volatile uint8_t *)&(SPI4->DR);
   return v;
}

Передача ОДНОГО байта. Ничего никуда не сдвигается. Возвращаемое значение можно обработать, а можно отбросить. Байт отправлен, байт получен. Всё.

Если с буферами - то код будет сложнее.
Симплексный обмен (отправляем команду, читаем данные, или отправляем команду, отправляем данные). Но никак не "Отправляем команду и данные и параллельно получаем ответ на прошлый запрос".
Спойлер
Код:
// Direction -> 0 = чтение / 1 = запись
// * Data - указатель на начало буфера
// Length - количество БАЙТ, которые нужно отправить.
void SPI_DMA_Data(uint8_t Direction, uint8_t * Data, uint16_t Length) {
   // Clean up DMA errors and statuses.
   DMA2->LIFCR = 0x0F200000;
   DMA2->HIFCR = 0x0000007D;
   if (Direction) {
      // From chip
      DMA2_Stream4->CR |=  (DMA_SxCR_MINC);
      DMA2_Stream4->M0AR = (uint32_t)Data;
      DMA2_Stream4->NDTR = Length;
      SPI5->CR2 |= SPI_CR2_TXDMAEN;
      DMA2_Stream4->CR |= DMA_SxCR_EN;
      while(DMA2_Stream4->CR & DMA_SxCR_EN) {};
      while (SPI5->SR & SPI_SR_FTLVL) {};
      while (SPI5->SR & SPI_SR_BSY) {};
      // Ignore SPI read
   } else {
      // To chip
      DevNull = 0;
      DMA2_Stream4->CR &= ~(DMA_SxCR_MINC);
      DMA2_Stream4->M0AR = (uint32_t)&(DevNull);
      DMA2_Stream4->NDTR = Length;
      DMA2_Stream3->M0AR = (uint32_t)Data;
      DMA2_Stream3->NDTR = Length;
      SPI5->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
      DMA2_Stream4->CR |= DMA_SxCR_EN;
      DMA2_Stream3->CR |= DMA_SxCR_EN;
      while(DMA2_Stream3->CR & DMA_SxCR_EN) {};
   }
   SPI5->CR2 &= ~(SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN);
}

// ВНИМАНИЕ. Пин /CS НЕ МЕНЯЕТ СВОЁ СОСТОЯНИЕ после выхода из функции.

Да, а захардкодил биты сброса флагов на DMA канале. Нет, мне не стыдно. И ещё я не проверяю их, потому что всё работает.


Вернуться наверх
 
Не в сети
 Заголовок сообщения: Re: stm32g4 - SPI, чтение из DR
СообщениеДобавлено: Ср авг 27, 2025 16:05:37 
Родился

Зарегистрирован: Ср авг 13, 2025 11:45:44
Сообщений: 10
Рейтинг сообщения: 0
AlanDrakes, спасибо!

натолкнули меня на вполне логичные мысли ("как же я сам не догадался"):

1. перед стравливанием RX надо дождаться полного завершения предыдущей передачи:
Код:
while (SPI4->SR & (SPI_SR_FTLVL | SPI_SR_BSY));

В отличие от ожидания RX, этот вариант не подвиснет, если передачи ещё не было.

2. Стравливание RX надо делать не после окончания передачи в TX, а перед очередной транзакцией

3. С учётом п.2, если выполняется только передача, перед поднятием CS не забывать дожидаться tx (п.1), об этом даже где-то в RM, кажись, было.

У вас немного не так, но это не важно. Мысли такие появились благодаря вашему коду, и они мне кажутся наиболее подходящими для большинства взаимодействия мастером со слейвами. Либо меня исправят более опытные товарищи.

Работает, вроде б, стабильно теперь. В т.ч. и на больших частотах. И в одну транзакцию, и разделяя отправку/получение.

Добавлено after 6 minutes 52 seconds:
Но вопрос про гарантии количества байт в RX остаётся открытым, т.к. в RM написано "четверть (1 байт) и более".. Но, видимо, по этому поводу никто не парится. Типа если в этот момент передача идёт по одному байту, то и в приёмнике больше одного появиться не может. CLK-ом ведь мастер рулит.


Вернуться наверх
 
Не в сети
 Заголовок сообщения: Re: stm32g4 - SPI, чтение из DR
СообщениеДобавлено: Чт авг 28, 2025 11:30:33 
Прорезались зубы
Аватар пользователя

Карма: 5
Рейтинг сообщений: 30
Зарегистрирован: Пн июл 04, 2016 16:51:22
Сообщений: 230
Откуда: Россия, Омск
Рейтинг сообщения: 0
Но вопрос про гарантии количества байт в RX остаётся открытым, т.к. в RM написано "четверть (1 байт) и более".. Но, видимо, по этому поводу никто не парится. Типа если в этот момент передача идёт по одному байту, то и в приёмнике больше одного появиться не может. CLK-ом ведь мастер рулит.

Верно. Шину тактирует как раз мастер, так что просто так байты он не получит.
В какой-то из реализаций я добавлял даже ресинхронизацию при возможной потере данных при передаче (делал клавиатуру отдельно от основного МК, потому что физически на разных платах), но там... было странно. И не уверен, что этот код вообще выполнялся.

А так - у меня сбои не наблюдались вроди бы ни разу с этим кодом. Особо долгие тесты не проводил, но в целом всё работало.

А "стравливание" принятого байта - так и процедура обмена намекает на то, что можно отправить и тут же принять байт за один вызов функции. Потому и дожидался полного завершения транзакции. Да, мне было лень обрабатывать крайние случаи. Но, хей, это работает.


Вернуться наверх
 
Показать сообщения за:  Сортировать по:  Вернуться наверх
Начать новую тему Ответить на тему  [ Сообщений: 4 ] 

Часовой пояс: UTC + 3 часа


Кто сейчас на форуме

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 19


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Русская поддержка phpBB
Extended by Karma MOD © 2007—2012 m157y
Extended by Topic Tags MOD © 2012 m157y