Неожиданные проблемы при стыковке STM32F103 и Atmega 88 по SPI

В качестве памятки себе, ну и может кто чего подскажет в коментах…

Не очень давно я выкладывал в личный блог заметку:о распиновке кнопок в ручке джойстика Saitek X52

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


Мастер: STM32F103C8T6, тактовая 72 МГц, выполняет роль контроллера джойстика и подключен к USB интерфейсу.
Ведомый: Atmega 88, тактовая 12МГц, выполняет роль контроллера кнопок в ручке джоя.
Тактовая частота SPI — порядка 280 КГц. Транзакция — 4 байта данных.

Мастер шлет запросы и отправляет результат в USB
Ведомый — берет значения кнопок и отправляет мастеру в ответ на запрос.

Первое, что неприятно удивило со стороны Атмеги — некорректная работа в режиме слэйва SPI. Заключается оно в следующем: АРМ периодически шлет данные через ДМА непрерывными посылками, длиной 4 байта. Линия CS инициируется на каждую посылку отдельно, т.е. с синхронизацией данных вопросов нет. Атмега успешно принимает первый байт, выдает прерывание, но при попытке записать следующий байт в регистр данных SPI выдает ошибку (флаг занятости интерфейса) и отправляет вместо нужных данных последний принятый байт.
Пробовал самые разные настройки интерфейса на обоих контроллерах, порядок бит, фронты захвата и т.д. Никакого положительного результата не получил.
Решил все тем, что стал слать не посылки из 4х байт, а по 1 байту с задержкой между ними — и все заработало… Но осадочек остался…

Второе, что пока так и не победил, непонятный шум и сдвиг младшего бита данных при приеме на STM32. То есть, допустим, от ведомого к мастеру идет посылка 0xFE,0xFF,0x7F,0xFF. Мастер же принимает ее как 0xFE, 0xFE,0x7F,0xFF. При этом младший бит первого байта убегает в младший бит второго байта (при нажатии кнопки 0 джойстика изменяется принимаемый бит второго байта, хотя лог. анализатор, подключенный к линии показывает корректные значения байт идущих по интерфейсу. Данные искажаются даже в процессе ручной работы с SPI в режиме отладки.
Может конечно где-то непропай, т.к. некоторое время (после отключения лог. анализатора) все работало как надо, но потом опять упало. Победить не смог. Пока в командировке, проверить еще раз и перепаять не могу…

UPD:
В процессе обсуждения в комментах выяснилась интересная деталь по поводу Меги. через неделю буду дома, проверю и отпишу здесь о результатах…

UPD2:
Времени на эксперименты было немного, побыстрому поменял обработчик прерывания, поставив сначала запись в SPDR а потом уже чтение. При непрерывной посылке в 4 байта все равно к мастеру передается корректно только первыц байт. остальную часть передачи занимают принятые байты…

UPD3:
Наконец дошли руки до переделки слэйва, как я и задумывал, на STM32.
В качестве камня поставил STM32F103T4U. Можно конечно было сунуть чего-нибудь помельче, в смысле мощности, но я решил не плодить у себя номенклатуру.
Повесил слэйв на DMA. Завелось все с полпинка. В теле программы кроме инициализации переферии и буфера передачи ничего больше небыло, а связь сразу зработала стабильно.
  • 0
  • 23 января 2014, 15:17
  • Ultrin

Комментарии (57)

RSS свернуть / развернуть
1. DMA шлет данные непрерывно, а принимающему контроллеру (без DMA) нужно больше времен (между отправкой байт) на их прем и обработку. Так что решение с задержкам логичное.
2. В отладке «ручной» такие вещи могут работать некорректно, лучше использовать для отладки в этом случает терминал.
0
1. По таймингам все вполне укладывается в рамки. т.е. обработка прерывания и запись в регистр данных происходит в соответствующей фазе SCK, когда и должен меняться следующий бит у приемника-передатчика.
2. эти данные 100% бьют с тем, что я получаю как в «прогоне» между брекпоинтами так и с тем, что мне по факту приходит в USB при работе контроллера в штатном режиме, а не в режиме отладки.
0
Ну запись в регистр я не сомневаюсь что идет без проблем, там аппаратная обработка, а вот дальше успевает ли обработать данные МК (считать байт до прихода следующего)…
0
считать он все успевает. я для контроля даже светодиодом мигал и смотрел все это на риголе. он не записывает новые данные в регистр, считая, что интерфейс занят. Стоит разбить непрерывную посылку на отдельные байты -и все работает. В этом и нестыковка, потому как нигде в спецификации spi не сказано, что между байтами надо делать паузы или дергать ногой cs.
0
А зачем нужен еще и авр? Неужели арм сам не справится с опросом кнопок?
0
  • avatar
  • PRC
  • 23 января 2014, 20:32
чтобы не тянуть 100500 проводов. конфигурация электроники ручки такая, что кнопки нельзя собрать в матрицу, а канал под кабельную линию довольно узкий.
0
А зачем целая мега88? Почему не, скажем, мега48? Или вообще STM32F100C4.
0
потому, что 88 у меня в чемодане лежит, а 48 или ф100 у меня не :D
0
нет*
0
Между 4-байтовыми посылками CS переходит в «1»? Если так, то у STM SPI неправильный — CS это сигнал сброса шины.
0
CS — вообще-то сигнал выбора слэйва и сброс шины тут не при делах. Да он переходит в 1 между посылками, но т.к. stm в режиме мастера -управление этой линии ручное, и к физическому интерфейсу самого камня имеет опосредованное отношение.
0
CS — вообще-то сигнал выбора слэйва и сброс шины тут не при делах.
Это понятно. Но spi в slave-е сбрасывается-то…
0
не сбрасывается, а переводит вход в z-состояние. к тому же между посылками как раз все нормально. проблема слэйва в приеме непрерывной серии. а у мастера глюк с младшим битом. частота для самого интерфейса мизерная.
0
точнее выход miso в z. входы как были, так и остались входами
0
Сбрасывается не весь МК, а только логика SPI-блока.
0
во первых, не сбрасывается она у меги, во вторых, какое это имеет отношение к приему непреиывной серии? между посылками все отрабатывается нормально.
0
не сбрасывается она у меги
Вы уверены?
When the SS pin is driven high, the SPI slave will immediately
reset the send and receive logic, and drop any partially received data in the Shift Register.
Изменение на SS сбрасывает счётчик бит SPI и переводит MISO в Z или на выход. Это не сброс?
0
какое это имеет отношение к описанным глюкам?
0
Ну откуда же мне знать? Глюки ведь в вашей программе и её текст вы не привели…
То что в MegaAVR SPI глючный — не верю. Хотя особенности могут быть. Но врядли — что может быть проще SPI?
+1
и я о том же. что может быть проще, чем запретить прерывание, считать байт в буфер, закинуть новый байт в регистр данных и разрешив прерывание выйти из обработчика? только не работает зараза. ставит флаг W и все.
текст не привожу, потому как он уже изменен, да и в данный момент я вообще с планшета пишу…
0
что может быть проще, чем запретить прерывание, считать байт в буфер, закинуть новый байт в регистр данных и разрешив прерывание выйти из обработчика
Это вы про программу для AVR? Тогда как-то неправильно.
Зачем запрещать прерывание? При вызове обработчика они и так запретятся. Первое прерывание от SPI будет по переходу SS в «0» — читать там ещё нечего. А байт на передачу можно положить и тогда когда SS=1.
0
Артем, Вы очень невнимательно читаете. первый байт уходит на ура. не уходит второй. вместо него уходит принятый. запрет прерываний на самом деле не критичен, но и без него все работает точно так же.
и я так и не понял, что по Вашему неправильно в том, что я описал
0
Ultrin, приведите код обработчика прерывания SPI в Mega — мне уже самому интересно, что там не так…
что по Вашему неправильно в том, что я описал
Вы лучше код дайте…
0
пишу по памяти, могу в какой букве ошибиться…

volatile u08 buf_in[4], buf_out[4],num;

SIGNAL(SIG_SPI)
{
    cli();
    buf_in[num] = SPDR;
    num++;
    num &= 0x03;
    SPDR = buf_out[num];
    sei();
    return;
};
0
На первый взгляд никакого криминала. Может и не лучшим образом, но работать вроде должно…
Так может и лучше, но в принципе тоже самое:

volatile u08 buf_in[4], buf_out[4],num;
SIGNAL(SIG_SPI)
{
    SPDR = buf_out[num];
    buf_in[num] = SPDR;
    num++;
    num &= 0x03;
}

Надо подумать вобщем…
0
вроде как мега не умеет разделять регистр данных на 2 буфера… сначала надо читать, потом писать…
0
вроде как мега не умеет разделять регистр данных на 2 буфера
Именно что вроде как…
0
сначала надо читать, потом писать…
Никогда не задумывался об этом моменте…
Посмотрел datasheet:
When configured as a Slave, the SPI interface will remain sleeping with MISO tri-stated as long as the SS pin is driven high. In this state, software may update the contents of the SPI Data Register, SPDR, but the data will not be shifted out by incoming clock pulses on the SCK pin until the SS pin is driven low. As one byte has been completely shifted, the end of transmission flag, SPIF is set. If the SPI interrupt enable bit, SPIE, in the SPCR Register is set, an interrupt is requested. The Slave may continue to place new data to be sent into SPDR before reading the incoming data.
Получается всё наоборот — сначала пишем, потом читаем.
P.S. Пост никак нельзя исправить на этом сайте что ли? Зря…
0
опа… чегой-то я это пропустил… надо будет потестить, как из командировки вернусь, спасибо.
0
поясню еще. CS переводится в 1 через некоторое время после того, как все 4 байта посылки уже приняты. потому можно ровным счетом наплевать, что в данный момент делает счетчик бит слэйва и в каком состоянии ного miso, т.к. слэйв на шине единственный.
0
а у мастера глюк с младшим битом
Именно с младшим? Может, он вообще байты целиком местами переставляет?
0
именно с младшим. потому как в тесте джоя в винде, вместо 0 кнопки срабатывает 8, а остальные все правильно работают. тем более, что в какой-то момент оно примерно с полчаса работало как надо…
0
Кстати посмотрел ещё раз — думаю я был неправ.
0
Обработчик такой примерно должен быть(если чего не перепутал на ночь глядя):

enum SpiSlaveState_t {
	SpiWaitFallingEdge=0,
	SpiRxTx=1
};
volatile SpiSlaveState_t SpiSlaveState=SpiWaitFallingEdge;
volatile uint8_t SpiTxBuffer[4], SpiRxBuffer[4], SpiByteCounter;
volatile bool SpiPacketComplete;
OS_INTERRUPT void SPI_STC_vect()
{
	switch (SpiSlaveState) {
	case SpiWaitFallingEdge:
		SPDR=SpiTxBuffer[0];
		SpiByteCounter=0;
		SpiSlaveState=SpiRxTx;
		break;
	case SpiRxTx:
		{
			uint8_t spi_byte_counter=SpiByteCounter;
			SPDR=SpiTxBuffer[spi_byte_counter+1];
			SpiRxBuffer[spi_byte_counter++]=SPDR;
			if (spi_byte_counter==4) {
				spi_byte_counter=0;
				// 4 байта приняли, 5 - записали на передачу
				// но 5-й дропнется когда SS в 1 перейдёт
				SpiSlaveState=SpiWaitFallingEdge;
				SpiPacketComplete=true;
			}
			SpiByteCounter=spi_byte_counter;
		}
		break;
	}
}
0
Хотя switch не нужен — if-а достаточно…
Жаль нет прерывания по переходу SS в «1»… Тоже бы но помешало…
0
переход cs можно контролировать вне прерывания хоть вручную, хоть через внешнее прерывание по фронту. в обработчике это не актуально, потому что пауза между посылками достаточно большая (в 10ки раз больше длины посылки, в моем случае.

начальный кейс в моем случае тоже не нужен, потому как я разрешаю spi только когда cs=0 и левых прерываний у меня нет. остается только увеличить буфер на 1, чтоб глюков небыло и поменять местами 2 команды работы с SPDR.

кстати, а что такое OS_INTERRUPT? мне такое раньше не поподалось…
0
в обработчике это не актуально, потому что пауза между посылками достаточно большая (в 10ки раз больше длины посылки, в моем случае.
В обработчике это не получится сделать — он на фронт не реагирует, только на спад. Может его можно на прерывание PCINT2_vect повесить — не знаю не проверял… Не запараллеливать же ещё один пин на отключение.включение spi…
начальный кейс в моем случае тоже не нужен, потому как я разрешаю spi только когда cs=0
В принципе case 0 не обязателен — если первый передаваемый байт заранее известен, то его можно до обмена в SPDR записать или если пауза между спадом SS и началом передачи байта достаточная чтобы байт записать на передачу.
остается только увеличить буфер на 1, чтоб глюков небыло
Можно не увеличивать (если вы про буфер передачи) — данные же не переписываются, а только читаются. А вот буфер приёма контролировать надо — мало ли сколько байт мастер решит передать — друг килобайт пропихнёт в шину вместо 4-х байт — тоды будет «ой». А читать можно сколько угодно (хотя тоже возможны варианты — ограничители лучше иметь-таки).
кстати, а что такое OS_INTERRUPT? мне такое раньше не поподалось…
В данном случае этот макрос к делу отношения не имеет — просто открыл первый попавшийся проект в Eclipse. Проект из примеров scmrtos. А макрос такой:

#ifdef __INTR_ATTRS /* default ISR attributes from avr/interrupt.h */
# define    OS_INTERRUPT    extern "C" __attribute__((__signal__,__INTR_ATTRS))
#else
# define    OS_INTERRUPT    extern "C" __attribute__((__signal__))
#endif
0
Было что то подобное вначале с STM32 -> HC595 — посылка не слалась до конца там нужна проверка окончания отсылки и CS только софтовый.
0
cs софтовый. с 595 у меня тоже была какая-то проблема похожая, но сути уже не помню… на тот момент я ее решил…
0
и если кнопки опрашивать можно было и UART обойтись — 2 проводка всего, вместо 4 SPI
0
+100500.
0
uart асинхронный. его сложней обрабатывать, т.к. в обратную сторону тоже идут некоторые данные. ну и в существующую линию я легко могу вставить 74hc163 и получить еще 8 кнопок до кучи…
0
А в сторону I2C не думал?
0
163 в i2c не вставить. и опять же сам парсинг протокола сложней
0
Зато туда можно вставить I2C GPIO Expander. Не помню партнум, но NXP такое точно рассылали.
0
надо погуглить, но такого у меня тоже в чемодане нет :(
0
16 bit I/O: PCA9575, 9671, 9673 ,9675.
40 bit I/O: PCA9505, 9506
0
спасибо, возьму на заметку
0
updated…
0
Так и не получилось наладить обмен?
Какой код тестировался последний раз?
Лучше приведите код master-а и slave…
0
Слэйв.

SIGNAL(SIG_SPI)
{
    cli();
    SPDR = buf_out[num];
    num++;
    buf_in[num] = SPDR;
    num &= 0x03;
    sei();
    return;
};


В мастере ничего особенного, указать буфер для ДМА и дернуть триггер начала передачи.

Если посылка состоит из непрерывной серии в 4 байта — то нормального обмена нет.
Если посылка состоит из раздельных 4хбайт — то обмен работает нормально.

К сожалению не было времени ковырять все детально, и появится оно еще не скоро. Уже решил заменить мегу на 36-ногий STM, как придет посылка. Тогда и ДМА должен работать как надо и мастеру не придется тупить и ожидать реакции от слэйва…
0
Слэйв.
Действительно — ничого особливого…
У вас отладчика для меги нет? Что она там принимает-передают вы по шигам посмотреть не можете?
0
нету. могу судить только косвенно по отправляемому обратно и выставляемым флагам.
0
Уже решил заменить мегу на 36-ногий STM, как придет посылка. Тогда и ДМА должен работать как надо
Думаете STM — панацея? Может быть и заработает сразу как надо… Не забудьте рассказать…
Если посылка состоит из непрерывной серии в 4
байта — то нормального обмена нет.
Если посылка состоит из раздельных 4хбайт — то обмен работает нормально.
О как…
Так может дело вовсе не slave, а в master-е?
У вас SPI-мастер на какой размер фрейма настроен? И ДМА как данные персылает? Байтами? Или словами?
Тут вопросов может быть больше чем к mega… :)
0
с ДМА и с SPI мастера как раз вопросов нет. потому как на логическом анализаторе шина читается отлично. Вопрос именно в слэйве. Данный алгоритм передачи непрерывных посылок прекрасно работает, например, с W5100, у которой обмен идет как раз по 4-ре байта на команду, причем на гораздо большей скорости.
0
на счет панацеи — посмотрим. Раз он умеет читать шину посредством ДМА значит не должно быть затыков с непрерывной посылкой, т.к. все это отработает на уровне жклеза.
0
UPD…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.