Форум РадиоКот https://radiokot.ru/forum/ |
|
stm32g4 - SPI, чтение из DR https://radiokot.ru/forum/viewtopic.php?f=59&t=198954 |
Страница 1 из 1 |
Автор: | Cliff [ Ср авг 27, 2025 10:55:39 ] |
Заголовок сообщения: | stm32g4 - SPI, чтение из DR |
Приветствую. Разбираюсь с работой 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 на каждом из этапом при выполнении команды. |
Автор: | AlanDrakes [ Ср авг 27, 2025 11:26:59 ] |
Заголовок сообщения: | Re: stm32g4 - SPI, чтение из DR |
Для 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 канале. Нет, мне не стыдно. И ещё я не проверяю их, потому что всё работает. |
Автор: | Cliff [ Ср авг 27, 2025 16:05:37 ] |
Заголовок сообщения: | Re: stm32g4 - SPI, чтение из DR |
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-ом ведь мастер рулит. |
Автор: | AlanDrakes [ Чт авг 28, 2025 11:30:33 ] |
Заголовок сообщения: | Re: stm32g4 - SPI, чтение из DR |
Но вопрос про гарантии количества байт в RX остаётся открытым, т.к. в RM написано "четверть (1 байт) и более".. Но, видимо, по этому поводу никто не парится. Типа если в этот момент передача идёт по одному байту, то и в приёмнике больше одного появиться не может. CLK-ом ведь мастер рулит. Верно. Шину тактирует как раз мастер, так что просто так байты он не получит. В какой-то из реализаций я добавлял даже ресинхронизацию при возможной потере данных при передаче (делал клавиатуру отдельно от основного МК, потому что физически на разных платах), но там... было странно. И не уверен, что этот код вообще выполнялся. А так - у меня сбои не наблюдались вроди бы ни разу с этим кодом. Особо долгие тесты не проводил, но в целом всё работало. А "стравливание" принятого байта - так и процедура обмена намекает на то, что можно отправить и тут же принять байт за один вызов функции. Потому и дожидался полного завершения транзакции. Да, мне было лень обрабатывать крайние случаи. Но, хей, это работает. |
Страница 1 из 1 | Часовой пояс: UTC + 3 часа |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |