SPI Slave в AtMega, логика работы, подводные камушки

Хотелось бы побольше статей про логику реализации тех или иных аппаратных функций в контроллерах, ибо по документации понять можно не все, приходится разбираться на практике, что иногда отнимает довольно много времени. Вспоминается STM32 с её всем горячо любимой реализацией I2C. Там если не сбросишь в нужный момент какой-нибудь статусный бит, то интерфейс повисает. Но речь сейчас не об этом.
Понадобилось мне сделать простейшую вещь: мега работает слейвом, причём только на отправку, мастер дергает её за ногу, читает нужное количество байт из буфера и отпускает. Причём буфер каждый раз должен читаться с начала, а количество считываемых данных не фиксировано. Казалось бы, что может быть проще, но тут вылезла парочка проблем.

Во-первых, сама реализация интерфейса не предоставляет никаких собственных возможностей отслеживания перехода ноги SS в низкий/высокий уровень, дабы сбрасывать счетчик отправки. Поставил в главный цикл обнуление при высоком уровне SS, ладно, пойдёт для нашей низкой скорости. Дальше — хуже. Логика работы интерфейса такая: по тактовому сигналу он сдвигает регистр на выход, когда счетчик битов обнуляется, то генерирует прерывание и перезагружает счетчик. Регистр не очищается при переходе SS в высокий, затем в низкий уровень, прерывание не генерируется, как удалось экспериментально установить, сбрасывается только счетчик битов. То есть чем это грозит: так как мы не знаем сколько байт будет читать мастер, то просто каждый раз просто кладем данные буфера по текущей позиции счетчика. Но когда мастер прочитал последний нужный ему байт, то вылезло прерывание и мы положили следующий. А когда он захочет снова прочитать то первым байтом ему вылезет не тот, который в буфере первым, а тот, который мы ему положили в конце, но он не прочитал. Эксперименты и попытки что-то с этим сделать привели к недетерминированному поведению, что меня больше всего расстроило, потому что такие глюки бывает сложно обнаружить. Самых простых(и надежных) решений тут два: использовать фиксированное количество байт на отправку или просто выкидывать первый байт. Я пробовал в основном цикле вместе с обнулением счетчика буфера записывать в регистр ещё и значение первого элемента. Оказалось плохой идеей: первый байт приходил нормально, но время от времени значение искажалось(сдвигалось на один бит), причины этого я не смог понять, потому что условия — сферические в вакууме, скорость интерфейса не более 30 кгц, частота контроллеров высокая, слейв больше ничем не занимался кроме проверки уровня на ноге SS. Ради эксперимента пробовал заставить мастер читать лишний бит, после последнего прочитанного байта(типо имитация помех, искажением количества тактов клока). Первый байт следующего чтения получался опять таки недетерминированным, то со сдвигом то без(точнее не помню, там были сдвиги но сколько), при этом я уже не писал в регистр вне прерывания. К слову, второй и последующие байты всегда приходили нормально, что не может не радовать.

Ну и вывод: что даже самое простое может заставить посидеть над реализацией. Возможные решения проблемы я уже высказал.
Получилось как-то сумбурно, без картинок и примеров кода, но надеюсь всё же доступно для понимания, и возможно даже поможет кому-нибудь, кто попытается реализовать что-то подобное.

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

RSS свернуть / развернуть
Может стоит выбрать другой МК?

В качестве возможного решения: на CS вешаете прерывание на подъем, в нем запрещаете и снова разрешаете SPI. По идеи помещённый байт должен быть выброшен и буффер отправки быть пустым. в него и помещаете первый байт.
0
Другой, это какой? Задача не принципиально нерешаемая, чтобы задумываться над тем, чтобы менять контроллер (а тем более это всё делается под конкретную плату и так далее). Отключать SPI была идея, но не пробовал в железе.
0
… зачем вообще нужно знать лог. уровень на SS?

… мастер сбросил SS, далее пошло тактирование (более медленное чем тактирование ядра) и слейв начал передачу данных. Когда байт передан — генерируется прерывание. Очень быстро (по сравнению с тактированием SPI) входим в обработчик и пихаем в буфер обмена любой байт (будь то первый или последний, это уже проблемы программиста). Если слейву не хватает времени на вычисления, повысьте частоту ядра или понизьте частоты SPI интерфейса.
Я пробовал в основном цикле вместе с обнулением счетчика буфера записывать в регистр ещё и значение первого элемента. Оказалось плохой идеей: первый байт приходил нормально, но время от времени значение искажалось(сдвигалось на один бит), причины этого я не смог понять, потому что условия — сферические в вакууме, скорость интерфейса не более 30 кгц, частота контроллеров высокая, слейв больше ничем не занимался кроме проверки уровня на ноге SS
… а настройка слейва и мастера идентичны?
0
вы не совсем поняли суть проблемы. Скорости хватает. Дело в том что: в спи в прерывание загоняется очередной байт, но мастер этот байт не вычитывает и освобождает шину. При последующем обращение (когда снова выбираем устройство) из слейва вычитывается этот подготовленный байт, а байт, который должен быть первым (даже если его попробывать загнать при освобождение шины, т.к. там уже находится «зарание подготовленный байт»).
0
… не, я понял. Но это не проблема SPI, примерно то же самое будит и с USART и пр. Поскольку обмен данными в SPI двухсторонний, то ничего не стоит посылать слейву, что то вроде старт байта и стоп байта (никаких потерь во времени). Но если рассматривать задачу: «как аппаратно обнаружить завершение сеанса», то тогда да, ваш метод с прерыванием по фронту вполне нормально.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.