Последовательный интерфейс SPI (3-wire). Последовательный интерфейс SPI Протокол spi

Теперь вы имеете общее представление о последовательном периферийном интерфейсе и можно перейти к рассмотрению SPI модуля.
SPI модуль микроконтроллера AVR atmega16 использует для своей работы 4 вывода - MOSI, MISO, SCK и SS. Когда модуль не задействован, эти выводы являются линиями портов ввода/вывода общего назначения. Когда модуль включен, режим работы этих выводов переопределяются согласно следующей таблице.

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

Регистры SPI модуля

В микроконтроллере atmega16 для работы с модулем SPI используются три регистра:

Управляющий регистр SPCR,
- статусный регистр SPSR,
- регистр данных SPDR.

Все три регистра восьмиразрядные.

Кофигурация модуля SPI устанавливается с помощью регистра SPCR (SPI Control Register).

SPIE – разрешает /запрещает прерывания от модуля SPI. Если бит установлен в 1, прерывания от SPI разрешены.

SPE – включает/выключает модуль SPI. Если бит установлен в 1, модуль SPI включен.

DORD – определяет порядок передачи данных. Когда бит установлен в 1, содержимое регистра данных передается младшим битом вперед. Когда бит сброшен, то старшим битом вперед.

MSTR – определяет режим работы микроконтроллера. Если бит установлен в 1, микроконтроллер работает в режиме Master (ведущий). Если бит сброшен – в режиме Slave (ведомый). Обычно микроконтроллер работает в режиме master.

CPOL и CPHA – определяют в каком режиме работает SPI модуль. Требуемый режим работы зависит от используемого периферийного устройства.


SPR1
и SPR0 – определяют частоту тактового сигнала SPI модуля, то есть скорость обмена. Максимально возможная скорость обмена всегда указывается в спецификации периферийного устройства.


Статусный регистр SPSR (SPI Status Register) предназначен для контроля состояния SPI модуля
, кроме того он содержит дополнительный бит управления скоростью обмена.


SPIF
– флаг прерывания от SPI. Он устанавливается в 1 по окончании передачи байта данных. Если разрешены прерывания модуля, одновременно с установкой этого флага генерируется прерывание от SPI. Также этот флаг устанавливается в 1 при переводе микроконтроллера из режима master в режим slave с помощью вывода SS.
Сброс флага происходит аппаратно, при вызове подпрограммы обработки прерывания или после чтения регистра SPSR с последующим обращением к регистру данных SPDR.

WCOL - флаг конфликта записи. Флаг устанавливается в 1, если во время передачи данных выполняется попытка записи в регистр данных SPDR. Флаг сбрасывается аппаратно после чтения регистра SPSR с последующим обращением к регистру данных SPDR.

SPI2X - бит удвоения скорости обмена. Установка этого разряда в 1 удваивает частоту тактового сигнала SCK. Микроконтроллер при этом должен работать в режиме master.

Взаимосвязь между битами SPR0, SPR1, SPI2X и частотой тактового сигнала SCK показана в таблице.

Где Fosc - тактовая частота микроконтроллера AVR.


Для передачи и приема данных предназначен регистр SPDR (SPI Data Register)
. Запись данных в этот регистр инициирует передачу данных SPI модулем. При чтении этого регистра, считывается содержимое буфера сдвигового регистра SPI модуля.

Программный код

Минимальный программный код для работы с SPI модулем состоит из двух функций:

Функции инициализации.
- функции передачи/приема байта данных

Инициализация SPI модуля

Инициализация включает в себя конфигурирование выводов SPI модуля и управляющего регистра SPCR.


#define SPI_PORTX PORTB
#define SPI_DDRX DDRB

#define SPI_MISO 6
#define SPI_MOSI 5
#define SPI_SCK 7
#define SPI_SS 4

/*инициализация SPI модуля в режиме master*/
void SPI_Init(void)
{

/*настройка портов ввода-вывода
все выводы, кроме MISO выходы*/
SPI_DDRX |= (1< SPI_PORTX |= (1<

/*разрешение spi,старший бит вперед,мастер, режим 0*/
SPCR = (1< SPSR = (0<}

Передача/прием данных

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

1. установка низкого логического уровня на линии SS
2. загрузка данных в регистр SPDR
3. ожидание окончания передачи (проверка флага SPIF)
4. сохранение принятых данных (чтение SPDR), если требуется
5. возврат на 2-ой шаг, если переданы не все данные
6. установка высокого логического уровня на линии SS

Ниже приведено несколько вариантов функции передачи/приема данных.

Передача одного байта данных по SPI


void SPI_WriteByte(uint8_t data)
{
SPI_PORTX &= ~(1< SPDR = data;
while(!(SPSR & (1< SPI_PORTX |= (1<}

Передача и прием одного байта данных по SPI

uint8_t SPI_ReadByte(uint8_t data)
{
uint8_t report;
SPI_PORTX &= ~(1< SPDR = data;
while(!(SPSR & (1< report = SPDR;
SPI_PORTX |= (1< return report;
}

Передача нескольких байтов данных по SPI
*data – указатель на массив передаваемых данных, а num – размерность массива


void SPI_WriteArray(uint8_t num, uint8_t *data)
{
SPI_PORTX &= ~(1< while(num--){
SPDR = *data++;
while(!(SPSR & (1< }
SPI_PORTX |= (1<}

//Пример использования:
uint8_t buf = {12, 43, 98};


SPI_WriteArray(3, buf);

Передачи и прием нескольких байтов данных по SPI
*data – указатель на массив передаваемых данных, а num – размерность массива.
Принятые данные будут сохраняться в том же массиве.


void SPI_ReadArray(uint8_t num, uint8_t *data)
{
SPI_PORTX &= ~(1< while(num--){
SPDR = *data;
while(!(SPSR & (1< *data++ = SPDR;
}
SPI_PORTX |= (1<}

Файлы

Простой SPI драйвер, в котором сведены все описанные выше функции -

SPI в Arduino- это один из основных протоколов для обмена данными между платой ардуино и подключенными устройствами. Вместе с I2C и UART этот протокол часто используется для многих типов периферийных устройств, поэтому знание принципов работы SPI необходимо любому инженеру-ардуинщику. В этой статье мы коротко рассмотрим основные принципы, схему взаимодействия и способ подключения SPI датчиков и экранов к Arduino.

SPI – это широко применяемый протокол передачи данных между микроконтроллером (Master) и периферийными устройствами (Slave). В наших проекта в качестве Master чаще всего используется плата Arduino. Интерфейс SPI был придуман и использовался компанией Motorola, но со временем стал отраслевым стандартом. Основным плюсом работы с этим интерфейсом считается высокая скорость и возможность подключения нескольких устройств на одной шине данных.

Выводы и контакты SPI

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

  • MOSI – по этой линии передается информация к Slave от Master.
  • MISO – используется для передачи информации к Master от Slave.
  • SCLK – создание тактовых импульсов для синхронной передачи данных.
  • SS – выбор ведомого устройства.

Взаимодействие SPI устройств

Взаимодействие устройств начинается, когда на выход SS подается низкий уровень сигнала.

Перед началом работы нужно определить:

  • С какого бита должен начинаться сдвиг – со старшего или с младшего. Регулируется порядок при помощи функции PI.setBitOrder().
  • Определить уровень, на котором должна находиться линия SCK при отсутствии тактового импульса. Регулируется функцией SPI.setDataMode().
  • Выбрать скорость передачи данных. Определяется функцией SPI.setClockDivider().

Следующим шагом будет определение, в каком режиме будет происходить передача информации. Выбор режима определяется такими показателями, как полярность и фаза тактового импульса. Если уровень низкий, записывается 0, высокий – 1. Всего существует 4 режима:

  • Режим 0 – SPI_MODE0: полярность (CPOL) 0, фаза (CPHA) 0.
  • Режим 1: полярность 0, фаза 1.
  • Режим 2:полярность 1, фаза 0.
  • Режим 3: полярность 1, фаза 1.

Изначально в Ардуино заложено, что данные передаются старшим битом вперед, но перед началом нужно уточнить это в документации. Продемонстрировать режимы можно на картинке.

Возможно два вида подключения в интерфейсе SPI: независимое и каскадное. В первом случае при подключении Master обращается к каждому Slave индивидуально, во втором случае подключение происходит по очереди, т.е. каскадно.

Подключение SPI к Ардуино

Для каждой модели Ардуино существую свои выводы для SPI. Эти выводы:

  • Uno: MOSI соответствует вывод 11 или ICSP-4, MISO – 12 или ICSP-1, SCK – 13 или ICSP-3, SS (slave) – 10.
  • Mega1280 или Mega2560: MOSI – 51 или ICSP-4, MISO – 50 или ICSP-1, SCK – 52 или ICSP-3, SS (slave) – 53.
  • Leonardo: MOSI – ICSP-4, MISO –ICSP-1, SCK –ICSP-3.
  • Due: MOSI – ICSP-4, MISO –ICSP-1, SCK –ICSP-3, SS (master) – 4, 10, 52.

Последний контроллер Arduino Due расширяет возможности пользователя и позволяет реализовать больше задач, чем на остальных микроконтроллерах. Например, можно автоматически управлять ведомым устройством и автоматически выбирать различные конфигурации (тактовая частота, режим и другие).

Библиотека SPI Arduino

Для работы на Ардуино создана отдельная библиотека, которая реализует SPI. Перед началом кода нужно добавить #include , чтобы включить библиотеку.

Основные функции:

  • begin() и end() – включение и выключение работы. При инициализации на выход настраиваются линии SCLK, MOSI и SS, подавая низкий уровень на SCLK, MOSI и высокий на SS. Функция end() не меняет уровни линий, она нужна для выключения блока, связанного с интерфейсом, на плате Ардуино.
  • setBitOrder(order) – установка порядка отправки битов информации (MSBFIRST – приоритет старшего бита, LSBFIRST – приоритет младшего бита).
  • setClockDivider(divider) – установка делителей тактов основной частоты. Можно поставить делители 2, 4, 8, 16, 32, 64 и 128. Записывается следующим образом – SPI_CLOCK_DIVn, где n – выбранный делитель.
  • setDataMode(mode) – выбор одного из четырех рабочих режимов.
  • transfer(value) – осуществление передачи байта от ведущего устройства и возвращение байта, который принят от ведомого устройства.
  • shiftIn(miso_pin, sclk_pin, bit_order) и shiftOut(mosi_pin, sclk_pin, order, value) – принятие и отправка данных, можно подключать к любым цифровым пинам, но перед этим нужно самостоятельно их настроить.

Преимущества и недостатки SPI

Преимущества интерфейса SPI:

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

Недостатки:

  • Большое количество выводов по сравнению с I2C.
  • Slave не может управлять потоком информации.
  • Отсутствие стандартного протокола обнаружения ошибок.
  • Большое количество способов реализации интерфейса.
  • Отсутствие подтверждения приема информации.

Пример использования SPI Ардуино в проекте с датчиком давления

Для реализации проекта нам нужны Ардуино, датчик давления макетная плата и провода. Пример подключения датчика изображен на рисунке.

При помощи датчика SCP1000 возможно узнавать такие параметры как давление и температура и передать эти значения через SPI.

Основные элементы скетча программы

В первую очередь в коде прописываются регистры датчика при помощи setup(). С устройства возвращаются несколько значений – одно в 19 бит для полученного давления, другое в 16 бит – для температуры. После этого происходит считывание двух температурных байтов и считывание давления в два этапа. Сначала программа берет три старших бита, затем следующие 16 бит, после чего при помощи побитового сдвига происходит объединение этих двух значений в одно. Настоящее давление – это 19-тиразрядное значение, деленное на 4.

const int PRESSURE = 0x1F; // первый этап определения давления (выявляются три старших бита)

const int PRESSURE_LSB = 0x20; // второй этап, в котором определяются 16 бит для давления

const int TEMPERATURE = 0x21; //16 бит для температуры

Для чтения данных температуры и преобразования ее в градусы Цельсия используется следующий элемент кода:

int tempData = readRegister(0x21, 2);

float realTemp = (float)tempData / 20.0; // чтобы определить реальное значение температуры в Цельсиях, нужно полученное число разделить на 20

Serial.print(“Temp

Serial.print(realTemp);

Считывание битов давления и объединение их:

byte pressure_data_high = readRegister(0x1F, 1);

pressure_data_high &= 0b00000111;

unsigned int pressure_data_low = readRegister(0x20, 2);

long pressure = ((pressure_data_high << 16) | pressure_data_low) / 4; //определение давления в Паскалях.

Краткие выводы о SPI

Экраны и датчики SPI часто встречаются в проектах ардуино, поэтому нужно знать, как работает этот протокол. В принципе, ничего сложного в подключении SPI устройств нет. Главное, правильно подсоединить провода и использовать методы стандартной библиотеки в нужной последовательности. Для некоторых устройств, например, SD карты или OLED - экранов, альтернатив, в принципе, не существует.

В этой статье я хочу провести краткий обзор шины SPI (интерфейса, широко распространённого во встраиваемой технике, используемого для подключения различных устройств) и попытаюсь описать процесс создания драйвера протокольного уровня SPI устройства для Linux. Данный документ не претендует на роль полного руководства, а скорее преследует цель указать нужное направление. Так как статья не вошла в размер одного топика, мне пришлось разбить её на две части.

0. Вместо введения

Что это за статья?
Эта статья представляет собой компиляцию информации из различных источников, вольный перевод некоторых частей документации, а также мои собственные комментарии, дополнения и описания возникших проблем.

Для кого эта статья?
В первую очередь, для новичков, каковым являюсь и я. На форумах по embedded Linux очень часто можно встретить вопрос: «А как на этой плате работать с SPI?». Именно на него я и попытаюсь дать ответ. В качестве примера, я приведу код написанный для работы с моим тестовым SPI устройством.

Структура статьи
По причине того, что информации получилось достаточно много, статья разбита на несколько подразделов:

  1. Что такое SPI?
  2. Обзор SPI подсистемы в Linux
  3. Разработка userspace протокольного SPI драйвера с использованием spidev
  4. Разработка протокольного SPI драйвера уровня ядра
  5. Документация
Первые два пункта войдут в первую часть статьи, оставшиеся во вторую.

Первый подраздел описывает работу шины SPI, данная часть статьи конкретно к Linux никак не привязана, поэтому её можно читать тем, кому Linux не интересен, а нужно лишь получить информацию об этом интерфейсе.

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

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

Ошибки
Я не волшебник, я только учусь. Если найдёте какие-либо ошибки или неточности, пожалуйста, сообщите мне.

1. Что такое SPI?

Аббревиатура SPI означает «Serial Peripheral Interface» или в русском варианте «последовательный периферийный интерфейс». Название говорит само за себя, данный интерфейс используется для работы с различными периферийными устройствами. Например, это могут быть различные ЦАП/АЦП, потенциометры, датчики, расширители портов ввода/вывода (GPIO), различная память и даже более сложная периферия, такая как звуковые кодеки и контроллеры Ethernet.

С технической точки зрения SPI - это синхронная четырёхпроводная шина. Она представляет собой соединение двух синхронных сдвиговых регистров, которые является центральным элементом любого SPI устройства. Для соединения используется конфигурацию ведущий/ведомый. Только ведущий может генерировать импульсы синхронизации. В схеме всегда только один ведущий (в отличие от той же шины I2C, где возможен вариант с более чем одним ведущим), количество ведомых может быть различно. В общем случае выход ведущего соединяется со входом ведомого, и наоборот, выход ведомого соединяется со входом ведущего. При подаче импульсов синхронизации на выход SCK, данные выталкиваются ведущим с выхода MOSI, и захватываются ведомым по входу MISO. Таким образом если подать количество импульсов синхронизации соответствующее разрядности сдвигового регистра, то данные в регистрах обменяются местами. Отсюда следует что SPI всегда работает в полнодуплексном режиме. А вот нужны ли нам данные, полученные от устройства при записи какого-либо параметра, это уже другой вопрос. Часто бывает что данные полученные от устройства при записи в него данных являются мусором, в таком случае их просто игнорируют, но мы их получим вне зависимости от нашего желания.

Контроллер SPI, как правило, реализуется периферийным блоком в MCU или eMPU. В большинстве чипов он может работать как в режиме ведущего, так и в режиме ведомого. Но на данный момент Linux поддерживает только режим ведущего (Master).

Существует несколько способов включения SPI устройств.

Простейший из них вы видите на рисунке выше (спасибо Wikipedia за рисунки под свободной лицензией GFDL). В данном случае к ведущему все ведомые подключаются параллельно, за исключением сигнала выбора ведомого (~CS). Для каждого ведомого необходим отдельный сигнал выбора ведомого (на рисунке они обозначены как SSx). Для сигналов выбора ведомого могут использоваться как специально предназначенные для этого выходы SPI-контроллера, так и порты ввода/вывода общего назначения (GPIO) микроконтроллера.

Два проводника используются для передачи данных, один для подачи тактовых импульсов и по одному сигналу выбора ведомого для каждого из ведомых.
Описание используемых сигналов:

  • MOSI - Master Output, Slave Input (выход ведущего, вход ведомого). Данный сигнал предназначен для последовательной передачи данных от ведущего к ведомому. Также может называться SDO, DO и т.п.
  • MISO - Master Input, Slave Output (вход ведущего, выход ведомого). Данный сигнал предназначен для последовательной передачи данных от ведомого к ведущему. Может называться SDI, DI и т.п.
  • SCK - Serial Clock (сигнал синхронизации). Используется для синхронизации при передаче данных. Также может иметь название SCLK, CLK и др.
  • ~CS - Chip Select (выбор микросхемы). С помощью данного сигнала происходит активация ведомого устройства. Обычно он является инверсным, то есть низкий уровень считается активным. Иногда его называют ~SS (Slave Select, рус. «выбор ведомого»).

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

Основное неудобство при независимом подключении ведомых в том, что для каждого из ведомых необходим отдельный сигнал ~CS. Каскадная схема подключения, в зарубежной литературе называемая «daisy-chain» (можно перевести как «гирлянда»), лишена такого недостатка.

Как видно из рисунка выше, здесь используется общий сигнал выбора ведомого для всех ведомых. Выход каждого из ведомых соединяется со входом следующего. Выход последнего ведомого соединяется со входом ведущего, таким образом образуется замкнутая цепь. При таком подключении можно считать что последовательно соединённые устройства образуют один большой сдвиговый регистр. Соответственно, данные можно записать во все устройства «за один присест», предварительно собрав нужный пакет, объединяющий данные для каждого из устройств в порядке соответствующем физическому порядку соединения. Но тут есть один тонкий момент. Во-первых, все микросхемы должны поддерживать такой тип подключения; во-вторых, ядро Linux не поддерживает такой тип подключения, так что если всё же захотите его использовать, то вам придётся модифицировать существующие драйвера, либо же написать собственные.

Существует четыре режима работы SPI устройств. Как правило, именно они вызывают больше всего путаницы у новичков. Данные четыре режима представляют собой комбинацию двух бит:

  • CPOL (Clock Polarity) - определяет начальный уровень (полярность) сигнала синхронизации.
    CPOL=0 показывает, что сигнал синхронизации начинается с низкого уровня, так что передний фронт является нарастающим, а задний - падающим.
    CPOL=1, сигнал синхронизации начинается с высокого уровня, таким образом передний фронт является падающим, а задний - нарастающим.
  • CPHA (Clock Phase) - фаза синхронизации, определяет по какому из фронтов синхронизирующего сигнала производить выборку данных.
    CPHA=0 показывает что необходимо производить выборку по переднему фронту, а
    CPHA=1 показывает что выборку данных необходимо производить по заднему фронту.
Эти два бита и образуют номер режима. CPOL является старшим битом, а CPHA - младшим. Иногда в документации к устройству явно не указывают номер режима, но его всегда можно легко определить по временным диаграммам. Также важно понимать, что выборка и установка данных всегда происходят по противоположенным фронтам синхронизирующего сигнала. Например, пусть наше устройство работает в режиме 0 (наиболее распространённый вариант), в таком случае ведомое устройство будет считывать бит данных со входа MOSI по переднему нарастающему фронту синхронизирующего сигнала, а ведущее устройство будет считывать данные от ведомого на входе MISO также по переднему нарастающему фронту. Для большей наглядности я приведу осциллограммы для всех четырёх режимов работы:

На этом рисунке показаны сигналы MOSI (синяя линия) и SCK (жёлтая линия). Во всех случаях передаётся число 0x64. Светлые вертикальные линии показывают момент выборки данных. Рассмотрим режим 2, для которого, как мы помним, CPOL=1, а CPHA=0. Таким образом мы видим что синхронизирующий сигнал изначально имеет высокий уровень, а выборка производится по переднему фронту (в данном случае спадающему). Так как осциллограф у меня имеет только два канала, сигналы ~CS и MISO не показаны. Но в данном случае они не так интересны, например, сигнал ~CS представляет собой просто «провал» на всём протяжении передачи данных.

2. Обзор SPI подсистемы в Linux

Драйверы SPI в Linux делятся на две части. Первая - это драйверы SPI контроллеров, которые работают непосредственно с железом конкретно взятого контроллера. Такие драйверы определяют как настроить контроллер, какие действия предпринять при переходе в режим пониженного энергопотребления (suspend) и выходе из него(resume), выбор следующей передачи (spi_transfer) из очереди передач в сообщении (spi_message, об очередях чуть ниже) и отправка его непосредственно в порт, также определяется как активировать/деактивировать конкретное устройство посредством CS (функции cs_activate/cs_deactivate). В этой статье я не буду описывать данный тип драйверов. Как правило, они уже реализованы для тех MCU/eMPU на которые существует порт Linux, и лезть в них руками надо только в том случае, если вам нужна какая-то специфичная функция, вроде Chip Select Decoding, для возможности активации нужного ведомого устройства посредством внешней логики. Иногда это бывает полезно, например, в случае недостатка GPIO.

Вторая часть - это протокольные драйверы, используемые для работы с различными ведомыми устройствами, которые подключены к шине SPI. Данные драйверы называют «протокольными», потому что они лишь отправляют и получают различные данные от ведомых устройств, при этом не работая напрямую с каким-либо оборудованием. Именно данный тип драйверов нам наиболее интересен, так как позволяет добавить поддержку интересующего ведомого устройства в систему, его то мы и рассмотрим.

Большинство протокольных драйверов представляет собой модули ядра. Например, если устройство представляет собой аудиокодек подключаемый по SPI, то драйвер будет также использовать функции предоставляемые ALSA, а программы (например, madplay) смогут работать с ним посредством символьного устройства /dev/audio, не имея ни малейшего понятия о том как он аппаратно устроен и к какой шине подключен.

Также ядро предоставляет протокольный драйвер общего назначения, называемый spidev, с интерфейсом в виде символьного устройства. Он позволяет совершать полудуплексные обращения к ведомому SPI-устройству посредством стандартных системных вызовов read() и write(), устанавливать режим работы, а также производить полнодуплексный обмен данными посредством ioctl() вызовов.

Таким образом протокольные драйверы для SPI устройств можно разделить на два типа:

  • userspace драйверы, работающие в пространстве пользователя и представляющие собой обычные программы на любом языке, работающие с SPI устройством посредством чтения/записи соответствующего символьного устройства spidev.
  • драйверы, работающие в пространстве ядра и предоставляющие интерфейс для userspace посредством файлов устройств в каталоге /dev, либо с помощью атрибутов в каталоге устройства в sysfs.
Все обращения к SPI устройствам Linux ставит в очередь. Протокольные драйверы SPI оперируют явно или не явно сообщениями представленными структурой struct spi_message, которая является мультисегментной SPI транзакцией.
struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned actual_length; int status; struct list_head queue; void *state; };
transfers - связанный список передаваемых сегментов в транзакции (передач);
spi - указатель на spi устройство, в очереди которого стоит данное сообщение;
is_dma_maped - если данный флаг «истина», то предоставлены оба, dma и cpu виртуальные адреса для каждого буфера передачи;
complete - обратный вызов, вызываемый для извещения об окончании транзакции;
context - аргумент для обратного вызова complete();
actual_length - полное число байт, которые были переданы во всех успешных предачах;
status - 0 в случае успеха, либо отрицательное значение с errno в случае ошибки;

Теги:

  • linux
  • spi
  • spidev
  • embedded
  • kernel module
Добавить метки

Введение

SPI (3-wire) - популярный интерфейс для последовательного обмена данными между микросхемами. Интерфейс SPI, наряду с I 2 C, относится к самым широко-используемым интерфейсам для соединения микросхем. Изначально он был придуман компанией Motorola, а в настоящее время используется в продукции многих производителей. Его наименование является аббревиатурой от "Serial Peripheral Bus", что отражает его предназначение - шина для подключения внешних устройств. Шина SPI организована по принципу "ведущий-подчиненный". В качестве ведущего шины обычно выступает микроконтроллер, но им также может быть программируемая логика, DSP-контроллер или специализированная ИС. Подключенные к ведущему шины внешние устройства образуют подчиненных шины. В их роли выступают различного рода микросхемы, в т.ч. запоминающие устройства (EEPROM, Flash-память, SRAM), часы реального времени (RTC), АЦП/ЦАП, цифровые потенциометры, специализированные контроллеры и др.

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

Электрическое подключение

Существует три типа подключения к шине SPI, в каждом из которых участвуют четыре сигнала (их основное и альтернативные обозначения см. в табл. 1). Самое простое подключение, в котором участвуют только две микросхемы, показано на рисунке 1. Здесь, ведущий шины передает данные по линии MOSI синхронно со сгенерированным им же сигналом SCLK, а подчиненный захватывает переданные биты данных по определенным фронтам принятого сигнала синхронизации. Одновременно с этим подчиненный отправляет свою посылку данных. Представленную схему можно упростить исключением линии MISO, если используемая подчиненная ИС не предусматривает ответную передачу данных или в ней нет потребности. Одностороннюю передачу данных можно встретить у таких микросхем как ЦАП, цифровые потенциометры, программируемые усилители и драйверы. Таким образом, рассматриваемый вариант подключения подчиненной ИС требует 3 или 4 линии связи. Чтобы подчиненная ИС принимала и передавала данные, помимо наличия сигнала синхронизации, необходимо также, чтобы линия SS была переведена в низкое состояние. В противном случае, подчиненная ИС будет неактивна. Когда используется только одна внешняя ИС, может возникнуть соблазн исключения и линии SS за счет жесткой установки низкого уровня на входе выбора подчиненной микросхемы. Такое решение крайне нежелательно и может привести к сбоям или вообще невозможности передачи данных, т.к. вход выбора микросхемы служит для перевода ИС в её исходное состояние и иногда инициирует вывод первого бита данных.


Рис. 1. Простейшее подключение к шине SPI

При необходимости подключения к шине SPI нескольких микросхем используется либо независимое (параллельное) подключение (рис. 2), либо каскадное (последовательное) (рис. 3). Независимое подключение более распространенное, т.к. достигается при использовании любых SPI-совместимых микросхем. Здесь, все сигналы, кроме выбора микросхем, соединены параллельно, а ведущий шины, переводом того или иного сигнала SS в низкое состояние, задает, с какой подчиненной ИС он будет обмениваться данными. Главным недостатком такого подключения является необходимость в дополнительных линиях для адресации подчиненных микросхем (общее число линий связи равно 3+n, где n-количество подчиненных микросхем). Каскадное включение избавлено от этого недостатка, т.к. здесь из нескольких микросхем образуется один большой сдвиговый регистр. Для этого выход передачи данных одной ИС соединяется со входом приема данных другой, как показано на рисунке 3. Входы выбора микросхем здесь соединены параллельно и, таким образом, общее число линий связи сохранено равным 4. Однако использование каскадного подключения возможно только в том случае, если его поддержка указана в документации на используемые микросхемы. Чтобы выяснить это, важно знать, что такое подключение по-английски называется "daisy-chaining".


Рис. 2. Независимое подключение к шине SPI


Рис. 3. Каскадное подключение к шине SPI

Протокол передачи

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

  • CPOL - исходный уровень сигнала синхронизации (если CPOL=0, то линия синхронизации до начала цикла передачи и после его окончания имеет низкий уровень (т.е. первый фронт нарастающий, а последний - падающий), иначе, если CPOL=1, - высокий (т.е. первый фронт падающий, а последний - нарастающий));
  • CPHA - фаза синхронизации; от этого параметра зависит, в какой последовательности выполняется установка и выборка данных (если CPHA=0, то по переднему фронту в цикле синхронизации будет выполняться выборка данных, а затем, по заднему фронту, - установка данных; если же CPHA=1, то установка данных будет выполняться по переднему фронту в цикле синхронизации, а выборка - по заднему). Информация по режимам SPI обобщена в таблице 2.

Ведущая и подчиненная микросхемы, работающие в различных режимах SPI, являются несовместимыми, поэтому, перед выбором подчиненных микросхем важно уточнить, какие режимы поддерживаются ведущим шины. Аппаратные модули SPI, интегрированные в микроконтроллеры, в большинстве случаев поддерживают возможность выбора любого режима SPI и, поэтому, к ним возможно подключение любых подчиненных SPI-микросхем (относится только к независимому варианту подключения). Кроме того, протокол SPI в любом из режимов легко реализуется программно.

Cравнение с шиной I 2 C

Как уже упоминалось, для стыковки микросхем не меньшей популярностью пользуется 2-проводная последовательная шина I 2 C . Ниже можно ознакомиться с преимуществами, которая дает та или иная последовательная шина.

Преимущества шины SPI Преимущества шины I2C
Предельная простота протокола передачи на физическом уровне обуславливает высокую надежность и быстродействие передачи. Предельное быстродействие шины SPI измеряется десятками мегагерц и, поэтому, она идеальна для потоковой передачи больших объемов данных и широко используется в высокоскоростных ЦАП/АЦП, драйверах светодиодных дисплеев и микросхемах памяти Шина I 2 C остается двухпроводной, независимо от количества подключенной к ней микросхем.
Все линии шины SPI являются однонаправленными, что существенно упрощает решение задачи преобразования уровней и гальванической изоляции микросхем Возможность мультимастерной работы, когда к шине подключено несколько ведущих микросхем.
Простота программной реализации протокола SPI. Протокол I2C является более стандартизованным, поэтому, пользователь I2C-микросхем более защищен от проблем несовместимости выбранных компонентов.

Производные и совместимые протоколы

  • MICROWIRE.

    Протокол MICROWIRE компании National Semiconductor полностью идентичен протоколу SPI в режиме 0 (CPOL = 0, CPHA = 0).

  • 3-проводной интерфейс компании Maxim

    Отличие этого интерфейса состоит в том, что вместо полнодуплексной передачи по двум однонаправленным линиям здесь выполняется полудуплексная передача по одной двунаправленной линии DQ.

  • QSPI

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

Кроме того, интерфейс SPI является основой для построения ряда специализированных интерфейсов, в т.ч. отладочный интерфейс JTAG и интерфейсы карт Flash-памяти, в т.ч. SD и MMC.

Табл. 1. Электрические сигналы шины SPI

Ведущий шины Подчиненный шины
Основное обозначение Альтернативное обозначение Описание Основное обозначение Альтернативное обозначение Описание
MOSI DO, SDO, DOUT MOSI DI, SDI, DIN
MISO DI, SDI, DIN Вход последовательного приема данных MISO DO, SDO, DOUT Выход последовательной передачи данных
SCLK DCLOCK, CLK, SCK Выход синхронизации передачи данных SCLK DCLOCK, CLK, SCK Вход синхронизации приема данных
SS CS Выход выбора подчиненного (выбор микросхемы) SS CS Вход выбора подчиненного (выбор микросхемы)

В этой статье я хочу провести краткий обзор шины SPI (интерфейса, широко распространённого во встраиваемой технике, используемого для подключения различных устройств) и попытаюсь описать процесс создания драйвера протокольного уровня SPI устройства для Linux. Данный документ не претендует на роль полного руководства, а скорее преследует цель указать нужное направление. Так как статья не вошла в размер одного топика, мне пришлось разбить её на две части.

0. Вместо введения

Что это за статья?
Эта статья представляет собой компиляцию информации из различных источников, вольный перевод некоторых частей документации, а также мои собственные комментарии, дополнения и описания возникших проблем.

Для кого эта статья?
В первую очередь, для новичков, каковым являюсь и я. На форумах по embedded Linux очень часто можно встретить вопрос: «А как на этой плате работать с SPI?». Именно на него я и попытаюсь дать ответ. В качестве примера, я приведу код написанный для работы с моим тестовым SPI устройством.

Структура статьи
По причине того, что информации получилось достаточно много, статья разбита на несколько подразделов:

  1. Что такое SPI?
  2. Обзор SPI подсистемы в Linux
  3. Разработка userspace протокольного SPI драйвера с использованием spidev
  4. Разработка протокольного SPI драйвера уровня ядра
  5. Документация
Первые два пункта войдут в первую часть статьи, оставшиеся во вторую.

Первый подраздел описывает работу шины SPI, данная часть статьи конкретно к Linux никак не привязана, поэтому её можно читать тем, кому Linux не интересен, а нужно лишь получить информацию об этом интерфейсе.

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

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

Ошибки
Я не волшебник, я только учусь. Если найдёте какие-либо ошибки или неточности, пожалуйста, сообщите мне.

1. Что такое SPI?

Аббревиатура SPI означает «Serial Peripheral Interface» или в русском варианте «последовательный периферийный интерфейс». Название говорит само за себя, данный интерфейс используется для работы с различными периферийными устройствами. Например, это могут быть различные ЦАП/АЦП, потенциометры, датчики, расширители портов ввода/вывода (GPIO), различная память и даже более сложная периферия, такая как звуковые кодеки и контроллеры Ethernet.

С технической точки зрения SPI - это синхронная четырёхпроводная шина. Она представляет собой соединение двух синхронных сдвиговых регистров, которые является центральным элементом любого SPI устройства. Для соединения используется конфигурацию ведущий/ведомый. Только ведущий может генерировать импульсы синхронизации. В схеме всегда только один ведущий (в отличие от той же шины I2C, где возможен вариант с более чем одним ведущим), количество ведомых может быть различно. В общем случае выход ведущего соединяется со входом ведомого, и наоборот, выход ведомого соединяется со входом ведущего. При подаче импульсов синхронизации на выход SCK, данные выталкиваются ведущим с выхода MOSI, и захватываются ведомым по входу MISO. Таким образом если подать количество импульсов синхронизации соответствующее разрядности сдвигового регистра, то данные в регистрах обменяются местами. Отсюда следует что SPI всегда работает в полнодуплексном режиме. А вот нужны ли нам данные, полученные от устройства при записи какого-либо параметра, это уже другой вопрос. Часто бывает что данные полученные от устройства при записи в него данных являются мусором, в таком случае их просто игнорируют, но мы их получим вне зависимости от нашего желания.

Контроллер SPI, как правило, реализуется периферийным блоком в MCU или eMPU. В большинстве чипов он может работать как в режиме ведущего, так и в режиме ведомого. Но на данный момент Linux поддерживает только режим ведущего (Master).

Существует несколько способов включения SPI устройств.

Простейший из них вы видите на рисунке выше (спасибо Wikipedia за рисунки под свободной лицензией GFDL). В данном случае к ведущему все ведомые подключаются параллельно, за исключением сигнала выбора ведомого (~CS). Для каждого ведомого необходим отдельный сигнал выбора ведомого (на рисунке они обозначены как SSx). Для сигналов выбора ведомого могут использоваться как специально предназначенные для этого выходы SPI-контроллера, так и порты ввода/вывода общего назначения (GPIO) микроконтроллера.

Два проводника используются для передачи данных, один для подачи тактовых импульсов и по одному сигналу выбора ведомого для каждого из ведомых.
Описание используемых сигналов:

  • MOSI - Master Output, Slave Input (выход ведущего, вход ведомого). Данный сигнал предназначен для последовательной передачи данных от ведущего к ведомому. Также может называться SDO, DO и т.п.
  • MISO - Master Input, Slave Output (вход ведущего, выход ведомого). Данный сигнал предназначен для последовательной передачи данных от ведомого к ведущему. Может называться SDI, DI и т.п.
  • SCK - Serial Clock (сигнал синхронизации). Используется для синхронизации при передаче данных. Также может иметь название SCLK, CLK и др.
  • ~CS - Chip Select (выбор микросхемы). С помощью данного сигнала происходит активация ведомого устройства. Обычно он является инверсным, то есть низкий уровень считается активным. Иногда его называют ~SS (Slave Select, рус. «выбор ведомого»).

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

Основное неудобство при независимом подключении ведомых в том, что для каждого из ведомых необходим отдельный сигнал ~CS. Каскадная схема подключения, в зарубежной литературе называемая «daisy-chain» (можно перевести как «гирлянда»), лишена такого недостатка.

Как видно из рисунка выше, здесь используется общий сигнал выбора ведомого для всех ведомых. Выход каждого из ведомых соединяется со входом следующего. Выход последнего ведомого соединяется со входом ведущего, таким образом образуется замкнутая цепь. При таком подключении можно считать что последовательно соединённые устройства образуют один большой сдвиговый регистр. Соответственно, данные можно записать во все устройства «за один присест», предварительно собрав нужный пакет, объединяющий данные для каждого из устройств в порядке соответствующем физическому порядку соединения. Но тут есть один тонкий момент. Во-первых, все микросхемы должны поддерживать такой тип подключения; во-вторых, ядро Linux не поддерживает такой тип подключения, так что если всё же захотите его использовать, то вам придётся модифицировать существующие драйвера, либо же написать собственные.

Существует четыре режима работы SPI устройств. Как правило, именно они вызывают больше всего путаницы у новичков. Данные четыре режима представляют собой комбинацию двух бит:

  • CPOL (Clock Polarity) - определяет начальный уровень (полярность) сигнала синхронизации.
    CPOL=0 показывает, что сигнал синхронизации начинается с низкого уровня, так что передний фронт является нарастающим, а задний - падающим.
    CPOL=1, сигнал синхронизации начинается с высокого уровня, таким образом передний фронт является падающим, а задний - нарастающим.
  • CPHA (Clock Phase) - фаза синхронизации, определяет по какому из фронтов синхронизирующего сигнала производить выборку данных.
    CPHA=0 показывает что необходимо производить выборку по переднему фронту, а
    CPHA=1 показывает что выборку данных необходимо производить по заднему фронту.
Эти два бита и образуют номер режима. CPOL является старшим битом, а CPHA - младшим. Иногда в документации к устройству явно не указывают номер режима, но его всегда можно легко определить по временным диаграммам. Также важно понимать, что выборка и установка данных всегда происходят по противоположенным фронтам синхронизирующего сигнала. Например, пусть наше устройство работает в режиме 0 (наиболее распространённый вариант), в таком случае ведомое устройство будет считывать бит данных со входа MOSI по переднему нарастающему фронту синхронизирующего сигнала, а ведущее устройство будет считывать данные от ведомого на входе MISO также по переднему нарастающему фронту. Для большей наглядности я приведу осциллограммы для всех четырёх режимов работы:

На этом рисунке показаны сигналы MOSI (синяя линия) и SCK (жёлтая линия). Во всех случаях передаётся число 0x64. Светлые вертикальные линии показывают момент выборки данных. Рассмотрим режим 2, для которого, как мы помним, CPOL=1, а CPHA=0. Таким образом мы видим что синхронизирующий сигнал изначально имеет высокий уровень, а выборка производится по переднему фронту (в данном случае спадающему). Так как осциллограф у меня имеет только два канала, сигналы ~CS и MISO не показаны. Но в данном случае они не так интересны, например, сигнал ~CS представляет собой просто «провал» на всём протяжении передачи данных.

2. Обзор SPI подсистемы в Linux

Драйверы SPI в Linux делятся на две части. Первая - это драйверы SPI контроллеров, которые работают непосредственно с железом конкретно взятого контроллера. Такие драйверы определяют как настроить контроллер, какие действия предпринять при переходе в режим пониженного энергопотребления (suspend) и выходе из него(resume), выбор следующей передачи (spi_transfer) из очереди передач в сообщении (spi_message, об очередях чуть ниже) и отправка его непосредственно в порт, также определяется как активировать/деактивировать конкретное устройство посредством CS (функции cs_activate/cs_deactivate). В этой статье я не буду описывать данный тип драйверов. Как правило, они уже реализованы для тех MCU/eMPU на которые существует порт Linux, и лезть в них руками надо только в том случае, если вам нужна какая-то специфичная функция, вроде Chip Select Decoding, для возможности активации нужного ведомого устройства посредством внешней логики. Иногда это бывает полезно, например, в случае недостатка GPIO.

Вторая часть - это протокольные драйверы, используемые для работы с различными ведомыми устройствами, которые подключены к шине SPI. Данные драйверы называют «протокольными», потому что они лишь отправляют и получают различные данные от ведомых устройств, при этом не работая напрямую с каким-либо оборудованием. Именно данный тип драйверов нам наиболее интересен, так как позволяет добавить поддержку интересующего ведомого устройства в систему, его то мы и рассмотрим.

Большинство протокольных драйверов представляет собой модули ядра. Например, если устройство представляет собой аудиокодек подключаемый по SPI, то драйвер будет также использовать функции предоставляемые ALSA, а программы (например, madplay) смогут работать с ним посредством символьного устройства /dev/audio, не имея ни малейшего понятия о том как он аппаратно устроен и к какой шине подключен.

Также ядро предоставляет протокольный драйвер общего назначения, называемый spidev, с интерфейсом в виде символьного устройства. Он позволяет совершать полудуплексные обращения к ведомому SPI-устройству посредством стандартных системных вызовов read() и write(), устанавливать режим работы, а также производить полнодуплексный обмен данными посредством ioctl() вызовов.

Таким образом протокольные драйверы для SPI устройств можно разделить на два типа:

  • userspace драйверы, работающие в пространстве пользователя и представляющие собой обычные программы на любом языке, работающие с SPI устройством посредством чтения/записи соответствующего символьного устройства spidev.
  • драйверы, работающие в пространстве ядра и предоставляющие интерфейс для userspace посредством файлов устройств в каталоге /dev, либо с помощью атрибутов в каталоге устройства в sysfs.
Все обращения к SPI устройствам Linux ставит в очередь. Протокольные драйверы SPI оперируют явно или не явно сообщениями представленными структурой struct spi_message, которая является мультисегментной SPI транзакцией.
struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned actual_length; int status; struct list_head queue; void *state; };
transfers - связанный список передаваемых сегментов в транзакции (передач);
spi - указатель на spi устройство, в очереди которого стоит данное сообщение;
is_dma_maped - если данный флаг «истина», то предоставлены оба, dma и cpu виртуальные адреса для каждого буфера передачи;
complete - обратный вызов, вызываемый для извещения об окончании транзакции;
context - аргумент для обратного вызова complete();
actual_length - полное число байт, которые были переданы во всех успешных предачах;
status - 0 в случае успеха, либо отрицательное значение с errno в случае ошибки;

Теги: Добавить метки