Периферийная ошибка STM32 SPI?

Я использую STM32F051R8T6 на плате STM32F0-Discovery и флэш-микросхему M25P80 в качестве периферийного устройства. У меня настроен SPI на 3 линии (+ 1 линия выбора программного чипа), полнодуплексный режим, и я использую опрос для отправки и получения данных. У меня было много проблем, пытаясь заставить SPI работать правильно, и все это сводилось к этой серии тестов, выполняемых последовательно, которые выявили ошибку, которую я не могу объяснить.

Шаг 1: Запросите подпись с периферийного устройства. Подпись 0x13, работает в 99% случаев.

Шаг 2: Включите запись на периферийное устройство, введя на него команду.

Шаг 3: Прочтите содержимое регистра состояния, чтобы убедиться, что прошла предыдущая команда.

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

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

@ 1 + 3:
0x00 0x00 0x02 0x13

@ 1 + 4:
0x00 0x00 0x02 0x13 0x00

@ 1 + 5:
0x00 0x00 0x02 0x13 0x02 0x00

@ 1 + 6:
0x00 0x00 0x02 0x13 0x02 0x00 0x02

@ 1 + 7:
0x00 0x00 0x02 0x13 0x02 0x00 0x02 0x02

А вот сигналы на линии MISO (красный) и SCK (желтый) при чтении 1 + 3 байта всего. Первый пакет — это второй шаг. Как и ожидалось, микросхема флэш-памяти ничего не передает. Второй пакет - шаг №. 3. Периферия молчит в течение первого байта, а затем выводит 0x02 на все последующие байты, но успешно читается только третий байт. И тогда не удается прочитать 4 и 6 байты (все дальнейшие байты читаются правильно).

введите описание изображения здесь

Так что я не знаю, как объяснить тот факт, что считываемые данные не полностью соответствуют тому, что я вижу на осциллографе. Самое странное, что на третьем шаге считывается байт 0x13! Если первый шаг удален, этот байт читается как 0x00. Похоже на повреждение памяти на стороне STM32, но я не могу найти причину этого, поскольку буферы очищаются с помощью memset(), а внутренний буфер SPI очищается с помощью HAL_SPI_FlushRxFifo(& hspi1).

Вот код:

uint8_t command;
uint8_t inBuffer[16];
uint8_t outBuffer[16];

/* Step 1: Check if IC is alive */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_RES;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, (1 + 3) + 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

for (volatile uint32_t i = 0; i < 200; i++);

/* Step 2: Enable writing */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_WREN;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_Transmit(&hspi1, outBuffer, 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

for (volatile uint32_t i = 0; i < 200; i++);

/* Step 3: Make sure writing is enabled */
{
  memset(inBuffer, 0x00, 16);
  memset(outBuffer, 0x00, 16);
  command = OPCODE_RDSR;
  outBuffer[0] = command;

  spi_select(M25P80);
  HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, 1 + 1, TIMEOUT);
  spi_deselect(M25P80);

  HAL_SPI_FlushRxFifo(&hspi1);
}

Некоторые подробности о SPI API STM. При вызове типа:

HAL_SPI_TransmitReceive(&hspi1, outBuffer, inBuffer, (1 + 3) + 1, TIMEOUT);

&hspi1 - peripheral handle  containing its settings and configurations
outBuffer - pointer to a memory location with data to be sent
inBuffer - pointer to a memory location to which received data is written
(1 + 3) + 1 - number of bytes to receive/transmit; 
              in this case 1 - command byte, 3 - dummy bytes, 1 - received signature byte
TIMEOUT - a value in the range of 10000 which indicates at which point transmission must be 
aborted if failed to finish

РЕДАКТИРОВАТЬ 1: Код инициализации SPI, сгенерированный STM32CubeMx:

hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLED;
HAL_SPI_Init(&hspi1);

РЕДАКТИРОВАТЬ 2: Теперь, когда я снизил скорость до 488 бит/с, все работает нормально. Я буду придерживаться этого взлома на некоторое время.

Ваш автобус выглядит довольно шумно. Для устранения неполадок я бы замедлил скорость шины до минимума и посмотрел, станут ли результаты более стабильными. Кроме того, часто команда Read-Enable работает только с самой следующей инструкцией. Я не уверен насчет этой конкретной памяти, но вы можете упустить свою возможность, прочитав регистр состояния.
Каковы настройки полярности и фазы часов? Можете ли вы показать нам свой код инициализации SPI?
@bitsmack скорость уже составляет 250 кГц, что является настолько низким, насколько я могу работать, не нарушая такты других периферийных устройств. Думаю, я действительно мог бы опуститься ниже для целей тестирования. По поводу отсутствия возможности прочитать регистр - это абсолютно не при чем, так как я вижу отсылаемые данные на осциллографе, проблема в том, что STM32 почему-то не читает их корректно.
@BruceAbbott Я обновил OP кодом инициализации SPI.
а) На самом деле вы не показываете 0x13 в своей волновой форме. Вы показываете только команду + три фиктивных байта, подпись отсутствует; б) Шина находится в состоянии HiZ во время фиктивных байтов, активируйте подтяжку на MISO; c) Проверьте настройку контактов и мультиплексирование; d) M25P80 должен синхронизировать данные по заднему фронту, ваша осциллограмма показывает переходы по переднему фронту, делайте из этого что хотите; e) Вы должны подождать 30 мкс после перехода RES в режим ожидания (см. техническое описание), прежде чем выпустить #CS. f) Выполните READ IDENTIFICATION, а не WREN, и сравните эти данные с ожидаемыми значениями.

Ответы (3)

На мой взгляд, вам следует использовать стандартную периферийную библиотеку для STM, потому что она более стабильна и не скрывает от вас низкоуровневые вещи, что дает вам более контролируемую среду. Также вам нужно быть осторожным с синхронизацией выводов CS, многие устройства требуют это значение некоторое время, пока вы не начнете передачу.

Команды, адреса или входные данные фиксируются по переднему фронту тактового входа, а выходные данные сдвигаются по заднему фронту тактового входа.

Не с SPI, нет. См. RM0091 для справки.
Извините, в техпаспорте M25P80 так написано. Означает ли это, что вы должны читать на 1EDGE, а писать на 2EDGE?
Clock: Входной сигнал C обеспечивает синхронизацию последовательного интерфейса. Команды, адреса или данные, присутствующие на вводе последовательных данных (DQ0), фиксируются по переднему фронту последовательных часов (C). Данные по DQ1 изменяются после спада C.
Ах, извините, я неправильно прочитал ваше исходное сообщение. Да, M25P80 сдвигает данные по заднему фронту. Какова ваша точка зрения?
Если STM32 читает по нарастающему фронту, а данные не начинаются до спадающего фронта, как вы читаете адрес? Читать 2 байта и <4??
Ну немного смещается на заднем фронте. На следующем нарастающем фронте производится выборка - прямо в средней точке. Единственное, что невозможно в этой конфигурации, это смещение данных ведомым устройством в самом первом бите сообщения, но это не то, чем я занимаюсь. Опять же, я не уверен, что точно понимаю, что вы говорите.
Это делает нас 2!!

Пытаться

hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;

В даташите на ваше устройство написано "и выходные данные доступны по заднему фронту C". Я считаю, что это то место, где вы бы это установили.