Драйвер SPI для Blackfin, работающий через DMA Печать
Добавил(а) microsin   

Здесь приведен перевод документации по библиотеке компании Analog Devices, реализующей физический драйвер устройства SPI (ADI_SPI_DMA) с использованием каналов DMA (документация в файле Blackfin\docs\drivers\spi\adi_spi_dma.pdf, находящемся в каталоге установки системы VisualDSP++). У драйвера нет никаких аппаратных привязок к определенному типу процессора (поскольку большинство, если не все процессоры Blackfin имеют на борту встроенный интерфейс SPI и систему прямого доступа к памяти - DMA).

Примечание: имеется аналогичный драйвер SPI, не использующий DMA, см. файл Blackfin\docs\drivers\spi\adi_spi_int.pdf (а также см. соответствующий перевод документации [6]). Примеры кода, использующие драйвер SPI с прерываниями и драйвером SPI с DMA, можно найти в поддиректориях Blackfin\Examples\ каталога установки VisualDSP++ (ищите файлы *.c и *.cpp, в которых подключается заголовочный файл adi_spi.h).

Драйвер устройства SPI был разработан для предоставления простого интерфейса к периферийным устройствам SPI процессора Blackfin. Команды, события и коды возврата в драйвере устройств могут быть использованы в программах приложений с целью установки обмена данными через SPI. Драйвер устройства SPI поддержан версиями как с использованием DMA, так и с использованием прерываний. Предоставляются два модуля исходного кода adi_spi_dma.c и adi_spi_int.c, где находятся соответственно версии драйвера SPI с поддержкой DMA и с поддержкой прерываний.

В этом документе описывается только та версия драйвера SPI, которая работает с DMA. Чтобы выбрать версию драйвера SPI с поддержкой DMA, код приложения должен:

· Подключить файл кода adi_spi_dma.c к списку файлов проекта.
· Использовать точку входа драйвера с прерываниями ADISPIDmaEntryPoint для открытия драйвера SPI.

[Используемые файлы]

Все упомянутые здесь файлы находятся в поддиректориях каталога установки системы программирования VisualDSP++. Указанные пути для подключаемых файлов начинаются с директории %ProgramFiles%\Analog Devices\VisualDSP 5.0\Blackfin\include, а для файлов исходного кода с директории %ProgramFiles%\Analog Devices\VisualDSP 5.0.

Подключаемые файлы. Исходный код драйвера подключает следующие заголовочные файлы:

services/services.h. Здесь содержатся все определения, прототипы функций и т. д. для всей библиотеки Системных Служб (System Services Library, SSL).

drivers/adi_dev.h. Здесь содержатся все определения, прототипы функций и т. д. для Менеджера Устройств (Device Manager) и общая информация по модели Драйвера Устройства.

drivers/spi/adi_spi.h. Здесь содержатся коды всех команд, событий и значений возврата для API драйвера устройства SPI.

Исходный код. Исходный код драйвера SPI содержится в файлах Blackfin/lib/src/drivers/spi/adi_spi.c и adi_spi_dma.c (файл adi_spi_dma.c по сути только заглушка, он только подключает код модуля adi_spi.c). Весь код написан на языке C. Для драйвера не используется никаких функций, написанных на ассемблере.

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

[Ресурсы, требуемые для драйвера SPI]

Драйверы устройств обычно требуют для своей работы некоторое количество системных ресурсов (уровни прерываний IVG, каналы DMA, память). В этом разделе описаны ресурсы, которые требует для себя драйвер SPI.

За исключением специально указанных ниже случаев, этот драйвер использует Системные Службы для доступа к любой требуемой аппаратуре процессора и управления этой аппаратурой. Информация в этой секции может быть полезна в определении ресурсов, которые нужно выделить для драйвера SPI со стороны Системных Служб, наподобие количества обработчиков прерывания или количества используемых каналов DMA и т. п.

По той причине, что в Драйверах Устройств и Системных Службах не используется динамическое выделение памяти, вся память для Драйверов Устройств и Системных Служб должна быть явно предоставлена приложением. Библиотека Драйверов Устройств и Системных Служб предоставляет специальные макросы, которые можно использовать в приложении для вычисления необходимого количества базовой памяти и/или количества дополнительной памяти, которая требуется для поддержки нужного функционала служб. Память для Менеджера Устройств и Системных Служб предоставляется специальными функциями инициализации соответствующего API (adi_xxx_Init(), где вместо xxx указывается мнемонический идентификатор службы. Например для Менеджера Устройств это будет функция adi_dev_Init()).

Прерывания. Этот драйвер SPI не использует прерывания.

DMA. Эта версия драйвера SPI использует один канал DMA, который может быть сконфигурирован либо для поддержки канала передачи SPI, либо канала приема SPI. Версию с DMA удобно использовать для пересылки больших массивов данных в одном направлении. Версию без DMA целесообразно использовать для коротких пересылок блоков данных неопределенной длины и одиночных байт, когда не ведется интенсивный обмен с устройством SPI, с возможностью передачи данных в обоих направлениях (передача данных в двух направлениях не поддерживается). Если нужна передача данных в двух направлениях, то используйте драйвер SPI, работающий по прерываниям [6].

Таймер. Драйвер SPI не требует использования таймера.

Часы реального времени (RTC). Драйвер устройства SPI не требует использования часов реального времени.

Программируемые флаги. Драйвер SPI не использует явно какие-либо выводы процессора (GPIO, компания Analog Devices почему-то называет их программируемыми флагами). Однако, на процессоре вход и выход SPI может быть мультиплексирован с программируемыми выводами GPIO. Пользователь должен сам убедиться в том, что эти выводы, используемые для SPI, не конфликтовали с использованием выводов GPIO, и наоборот.

Используемые выводы. Всего интерфейс SPI для соединения с внешним миром использует 4 сигнальных провода - 2 вывода данных MOSI и MISO, 1 вывод управления выборкой устройства SPISS и один вывод тактирования бит CLK. Подробнее про общее описание SPI и его сигналы см. [3]. Аппаратное описание периферийного устройства SPI и его используемые выводы в процессоре ADSP-BF538 см. в статье [4].

[Функции, поддерживаемые драйвером SPI]

Направление потоков данных. Драйвер поддерживает приведенные в таблице ниже варианты настройки для направления потока данных. ADI_DEV_DIRECTION это тип перечисления enum, задающий варианты направления данных (определен в заголовочном файле Blackfin\include\drivers\adi_dev.h).

Таблица 2. Поддерживаемые направления потока данных для устройства SPI (Dataflow Directions).

ADI_DEV_DIRECTION Описание
ADI_DEV_DIRECTION_INBOUND Поддерживает прием данных в устройство.
ADI_DEV_DIRECTION_OUTBOUND Поддерживает передачу данных из устройства.

Методы поддержки потока данных. Поддерживаемые методы организации потока данных перечислены в таблице ниже. ADI_DEV_MODE это тип перечисления enum, задающий варианты предоставления буферов для данных (определен в заголовочном файле Blackfin\include\drivers\adi_dev.h).

Таблица 3. Поддерживаемые методы организации потока данных.

ADI_DEV_MODE Описание
ADI_DEV_MODE_CHAINED Поддерживает метод цепочек буферов.
ADI_DEV_MODE_CHAINED_LOOPBACK Поддерживает метод цепочек буферов с переходом на начало цепочки.

Типы буфера. Эта версия драйвера поддерживает только один тип буфера - одномерный линейный ADI_DEV_1D_BUFFER. Поле pAdditionalInfo структуры буфера игнорируется.

Идентификаторы команд. В этой секции перечислены команды, поддерживаемые драйвером. Команды делятся на 3 секции. Первая описывает команды, поддерживаемые напрямую Менеджером Устройств (Device Manager, см. [5]). Следующая секция описывает общие поддерживаемые драйвером команды (относящиеся ко всем драйверам, не только к SPI). Последняя секция описывает специфические для драйвера SPI команды.

Команды посылаются в драйвер устройства через функцию adi_dev_Control(). Она принимает 3 аргумента:

DeviceHandle. Этот параметр типа ADI_DEV_DEVICE_HANDLE, который уникально идентифицирует драйвер устройства. Это хендл, предоставленный клиенту при вызове функции adi_dev_Open().

CommandID. Этот параметр типа u32, он задает идентификатор команды.

Value. Этот параметр типа void *, смысл которого зависит от значения идентификатора команды.

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

Команды, перечисленные ниже, поддерживаются напрямую Менеджером Устройств, и не передаются драйверу для обработки. Таким образом, все драйверы устройств поддерживают эти команды.

ADI_DEV_CMD_TABLE. Команда предоставляет таблицу пар команд, которые будут переданы драйверу. Value это указатель на таблицу пар команд (ADI_DEV_CMD_VALUE_PAIR *).   

ADI_DEV_CMD_END. Обозначает конец таблицы пар команд. Value игнорируется.

ADI_DEV_CMD_PAIR. Команда передает одну пару команда-значение. Value это указатель на одну пару команда-значение (ADI_DEV_CMD_PAIR *).

ADI_DEV_CMD_SET_SYNCHRONOUS. Эта команда разрешает/запрещает синхронный режим работы драйвера. Value – TRUE/FALSE.

Идентификаторы команд, описанные в этой секции, являются общими для многих драйверов устройств. Ниже в списке перечислены всех общие идентификаторы команд (command ID), которые поддерживаются драйвером SPI.

ADI_DEV_CMD_GET_2D_SUPPORT. Позволяет определить, поддерживает ли драйвер буферы 2D (двумерный буфер). Value - u32* (указывает на ячейку памяти, где будет сохранено значение TRUE/FALSE).

ADI_DEV_CMD_SET_DATAFLOW_METHOD. Задает используемый метод потока данных устройства. Value – одно из поддерживаемых значений перечисления ADI_DEV_MODE (таблица 3).

ADI_DEV_CMD_SET_STREAMING. Разрешает/запрещает режим стриминга драйвера. Valie - TRUE/FALSE.

ADI_DEV_CMD_GET_INBOUND_DMA_CHANNEL_ID. Возвратит идентификатор канала DMA для входящего канала DMA. Value - u32* (указывает на ячейку памяти, где будет сохранено значение идентификатора канала).

ADI_DEV_CMD_GET_OUTBOUND_DMA_CHANNEL_ID. Возвратит идентификатор канала DMA для исходящего канала DMA. Value - u32* (указывает на ячейку памяти, где будет сохранено значение идентификатора канала).

ADI_DEV_CMD_SET_INBOUND_DMA_CHANNEL_ID. Установит идентификатор канала DMA для входящего канала DMA. Value - u32 (значение идентификатора канала).

ADI_DEV_CMD_SET_OUTBOUND_DMA_CHANNEL_ID. Установит идентификатор канала DMA для исходящего канала DMA. Value - u32 (значение идентификатора канала).

ADI_DEV_CMD_GET_INBOUND_DMA_PMAP_ID. Возвратит PMAP ID - идентификатор периферийного устройства, привязанного к входящему каналу DMA. Value - u32* (указывает на ячейку памяти, где будет сохранено значение PMAP ID).

ADI_DEV_CMD_GET_OUTBOUND_DMA_PMAP_ID. Возвратит PMAP ID - идентификатор периферийного устройства, привязанного к исходящему каналу DMA. Value - u32* (указывает на ячейку памяти, где будет сохранено значение PMAP ID).

ADI_DEV_CMD_SET_DATAFLOW. Разрешает/запрещает поток данных через устройство. Value – TRUE/FALSE.

ADI_DEV_CMD_GET_PERIPHERAL_DMA_SUPPORT. Позволяет вызывающему коду определить, поддерживается ли драйвер периферийным DMA. В ответ на эту команду драйвер всегда вернет TRUE. Value – u32*, указывает на ячейку, куда помещается ответ (TRUE/FALSE).

ADI_DEV_CMD_SET_ERROR_REPORTING. Разрешает/запрещает сообщения от ошибках со стороны драйвера устройства. Value – TRUE/FALSE.

Идентификаторы команд, перечисленные ниже, поддерживаются и обрабатываются драйвером SPI, они уникальны именно для этого драйвера устройства.

ADI_SPI_CMD_SET_BAUD_RATE. Устанавливает скорость обмена (baud rate). Value = частота следования тактов в Гц.

ADI_SPI_CMD_SET_SLAVE_FLAG. Устанавливает регистр флагов подчиненного устройства (SPI slave flag register). Value = значение для регистра.

ADI_SPI_CMD_SET_CONTROL_REG. Напрямую устанавливает значение регистра управления SPI (SPIx_CTL). Value = значение для регистра.

ADI_SPI_CMD_SET_BAUD_REG. Напрямую устанавливает значение регистра скорости. Value = значение для регистра.

ADI_SPI_CMD_ENABLE_SLAVE_SELECT. Разрешает сигнал выборки подчиненного устройства. Value = номер подчиненного устройства.

ADI_SPI_CMD_DISABLE_SLAVE_SELECT. Запрещает сигнал выборки подчиненного устройства. Value = номер подчиненного устройства.

ADI_SPI_CMD_SELECT_SLAVE. Делает выборку подчиненного устройства. Value = номер подчиненного устройства.

ADI_SPI_CMD_DESELECT_SLAVE. Снимает выборку подчиненного устройства. Value = номер подчиненного устройства.

ADI_SPI_CMD_SET_EXCLUSIVE_ACCESS. Предотвращает постороннее использование этого устройства SPI. Value = TRUE означает эксклюзивный доступ, FALSE означает разрешение общего доступа.

ADI_SPI_CMD_SET_TRANSFER_INIT_MODE. Устанавливает значение TIMOD (принцип запуска процедуры переноса данных).

Value = 0 - запуск при чтении ядром регистра SPI_RDBR (приходящие данные под управлением прерываний).
Value = 1 - запуск при записи ядром регистра SPI_TDBR (исходящие данные под управлением прерываний).
Value = 2 - запуск чтением DMA регистра SPI_RDBR (приходящие данные под управлением DMA).
Value = 3 - запуск записью DMA регистра SPI_TDBR (исходящие данные под управлением DMA).

ADI_SPI_CMD_SEND_ZEROS. Настраивает отправку нулей, когда регистр передачи SPI_TDBR пуст. Value =TRUE означает отправку нулей, FALSE означает отправку последних данных, записанных в регистр.

ADI_SPI_CMD_SET_GET_MORE_DATA. Устанавливает режим получения данных, когда предыдущие данные еще не прочитаны. Value = TRUE означает, что старые данные будут перезаписываться новыми (gets more date), FALSE - новые приходящие данные будут отбрасываться.

ADI_SPI_CMD_SET_PSSE. Устанавливает управляющий бит PSSE. Value = TRUE означает, что SPISS разрешен, FALSE - SPISS запрещен.

ADI_SPI_CMD_SET_MISO. Разрешает/запрещает MISO. Value = TRUE - MISO разрешен, FALSE - MISO запрещен.

ADI_SPI_CMD_SET_WORD_SIZE. Устанавливает размер слова фрейма в битах. Value = 8 .. 16.

ADI_SPI_CMD_SET_LSB_FIRST. Команда настраивает очередность следования бит по старшинству (MSB/LSB). Value = TRUE - младший бит слова (LSB) будет отправлен первым, FALSE - первым будет отправлен MSB.

ADI_SPI_CMD_SET_CLOCK_PHASE. Устанавливает формат передачи (фаза тактов). Value = TRUE - начало с переключением, FALSE - переключение посередине.

ADI_SPI_CMD_SET_CLOCK_POLARITY. Устанавливает полярность тактов. Value = TRUE - активный уровень лог. 0, FALSE - активный уровень лог. 1.

ADI_SPI_CMD_SET_MASTER. Устанавливает режим работы в качестве главного (master) или подчиненного устройства (slave). Value = TRUE - master, FALSE – slave.

ADI_SPI_CMD_SET_OPEN_DRAIN_MASTER. Управляет битом WOM в регистре управления SPIx_CTL. Value = TRUE - открытый сток (open drain) выходов master, FALSE - обычные выходы.

ADI_SPI_CMD_EXECUTE_DUMMY_READ. Выполняет фиктивное чтение SPI. Value = 8 - 8-битное чтение, 16 - 16 битное чтение.

ADI_SPI_CMD_PAUSE_DATAFLOW. Ставит на паузу или снимает с паузы поток данных. Value = TRUE - пауза, FALSE - снятие с паузы.

ADI_SPI_CMD_SET_PIN_MUX_MODE. Устанавливает специфичный для процессора режим мультиплексирования выводов. Value = значение из перечисления типа ADI_SPI_PIN_MUX_MODE.

Семейство процессоров Значение перечисления ADI_SPI_PIN_MUX_MODE Комментарии
ADSP - BF50x ADI_SPI_PIN_MUX_MODE_0 Режим mode 0 (по умолчанию) мультиплексирования выводов SPI. Для SPI0 порт PF15 используется как выборка подчиненного устройства 3 (SPI0 SSEL3). Для SP1 порт PG1 используется как выборка подчиненного устройства 2 (SPI1 SSEL2), PG0 как выборка подчиненного устройства 3 (SPI1 SSEL3).
ADI_SPI_PIN_MUX_MODE_1 Режим mode 1 мультиплексирования выводов SPI. Для SPI0 порт PH0 используется как выборка подчиненного устройства 3 (SPI0 SSEL3). Для SP1 порт PG1 используется как выборка подчиненного устройства 2 (SPI1 SSEL2), PH1 как выборка подчиненного устройства 3 (SPI1 SSEL3).
ADI_SPI_PIN_MUX_MODE_2 Режим mode 2 мультиплексирования выводов SPI. Для SPI0 порт PF15 используется как выборка подчиненного устройства 3 (SPI0 SSEL3). Для SP1 порт PH2 используется как выборка подчиненного устройства 2 (SPI1 SSEL2), PG0 как выборка подчиненного устройства 3 (SPI1 SSEL3).
ADI_SPI_PIN_MUX_MODE_3 Режим mode 3 мультиплексирования выводов SPI. Для SPI0 порт PH0 используется как выборка подчиненного устройства 3 (SPI0 SSEL3). Для SP1 порт PH2 используется как выборка подчиненного устройства 2 (SPI1 SSEL2), PH1 как выборка подчиненного устройства 3 (SPI1 SSEL3).
Другие процессоры - Команда не поддерживается.

События callback. Ниже перечислены события для вызовов callback, когда они разрешены для генерации драйвером. События поделены на 2 врезки - общие события и события драйвера SPI. Первая описывает события, которые являются общими для многих драйверов устройств. Другая секция описывает идентификаторы событий (event ID), специфичных именно для драйвера SPI. Функция callback приложения должна быть подготовлена для обработки каждого из событий в этих врезках.

Callback-функция имеет тип ADI_DCB_CALLBACK_FN. В неё передается 3 параметра:

ClientHandle. Этот параметр имеет тип void*, его значение было передано драйверу устройства как параметр функции adi_dev_Open().

EventID. Это значение типа u32, которое указывает идентификатор события (event ID).

Value. У этого параметра тип void*, и смысл этого значения зависит от контекста события (от специфического значения event ID).

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

У этой версии драйвера есть особенность, о которой ни полслова не говорится в документации: callback-функция будет вызвана только в том случае, если в цепочке буферов будет не меньше 2 буферов. Т. е. если будет добавлена цепочка, состоящая только из одного буфера, то callback не будет вызван, хотя драйвер будет исправно работать.

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

ADI_DEV_EVENT_BUFFER_PROCESSED. Оно оповещает callback-функцию, что буфер I/O цепочки был обработан драйвером устройства. Это событие также используется для оповещения о том, что весь кольцевой буфер был обработан, если драйверу было указано генерировать вызов callback при завершении обработки всего кольцевого буфера. Value – для цепочечного метода потока данных это значение параметра CallbackParameter, которое было предоставлено в буфере, который был передан в API-функцию adi_dev_Read() или adi_dev_Write(). Для кольцевого метода это значение адреса буфера, предоставленного в API-функцию adi_dev_Read() или adi_dev_Write().

ADI_DEV_EVENT_DMA_ERROR_INTERRUPT. Оповещает callback-функцию, что произошла ошибка DMA. Value - NULL (не используется).

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

ADI_SPI_EVENT_TRANSMISSION_ERROR. Означает, что произошло детектирование события TXE. Value не используется.

ADI_SPI_EVENT_RECEIVE_ERROR. Было детектировано событие RBSY. Value не используется.

ADI_SPI_EVENT_MODE_FAULT_ERROR. Было детектировано событие MODF. Value не используется.

ADI_SPI_EVENT_TRANSMIT_COLLISION_ERROR. Было детектировано событие TXCOL. Value не используется.

ADI_SPI_EVENT_ERROR_INTERRUPT. Была сгенерирована ошибка SPI. ВНИМАНИЕ: это событие больше не используется и заменено индивидуальными событиями, которые были описаны выше. Value не используется.

[Коды возврата]

Все API-функции драйвера устройства возвращают статус, показывающий успешное выполнение функции или показывающий, какая произошла ошибка. Эта секция перечисляет коды возврата, которые драйвер SPI может возвратить приложению. Значение возврата ADI_DEV_RESULT_SUCCESS показывает успешное завершение, в то время как другое значение показывает ошибку или какой-то другой информативный результат. ADI_DEV_RESULT_SUCCESS всегда соответствует нулевому значению кода возврата. Все другие коды возврата соответственно будут ненулевые.

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

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

if (adi_dev_Xxxx(...) == ADI_DEV_RESULT_SUCCESS)
{
   // Нормальная обработка, ошибки нет
   ...
}
else
{
   // Обработка ошибки
   ...
} 

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

ADI_DEV_RESULT_SUCCESS. Выполнение функции было успешным.

ADI_DEV_RESULT_NOT_SUPPORTED. Эта функция не поддерживается драйвером.

ADI_DEV_RESULT_DEVICE_IN_USE. Запрошенное устройство уже используется.

ADI_DEV_RESULT_NO_MEMORY. Недостаточное количество доступной памяти.

ADI_DEV_RESULT_BAD_DEVICE_NUMBER. Недопустимый номер устройства.

ADI_DEV_RESULT_DIRECTION_NOT_SUPPORTED. Устройство не может быть открыто в заданном направлении.

ADI_DEV_RESULT_BAD_DEVICE_HANDLE. Недопустимый хендл для драйвера устройства.

ADI_DEV_RESULT_BAD_MANAGER_HANDLE. Недопустимый хендл для Менеджера Устройств.

ADI_DEV_RESULT_BAD_PDD_HANDLE. Недопустимый хендл для физического драйвера устройства.

ADI_DEV_RESULT_INVALID_SEQUENCE. Запрошенное действие не находится в допустимой последовательности.

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

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

ADI_DEV_RESULT_DATAFLOW_UNDEFINED. Еще не был декларирован метод поддержки потока данных.

ADI_DEV_RESULT_DATAFLOW_INCOMPATIBLE. Этот метод поддержки потока данных несовместим с запрошенным действием.

ADI_DEV_RESULT_BUFFER_TYPE_INCOMPATIBLE. Устройство не поддерживает этот тип предоставленного буфера.

ADI_DEV_RESULT_CANT_HOOK_INTERRUPT. Менеджер Прерываний [1] не смог подцепить обработчик прерывания.

ADI_DEV_RESULT_CANT_UNHOOK_INTERRUPT. Менеджер Прерываний [1] не смог отцепить обработчик прерывания.

ADI_DEV_RESULT_NON_TERMINATED_LIST. Предоставленная цепочка буферов не завершена NULL.

ADI_DEV_RESULT_NO_CALLBACK_FUNCTION_SUPPLIED. Потребовалась, но не была предоставлена callback-функция.

ADI_DEV_RESULT_REQUIRES_UNIDIRECTIONAL_DEVICE. Требуется, чтобы устройство было открыто только для одного направления трафика - либо только входящего, либо только для исходящего трафика.

ADI_DEV_RESULT_REQUIRES_BIDIRECTIONAL_DEVICE. Требуется, чтобы устройство было открыто только для двунаправленного трафика - и входящего, и исходящего.

Приведенные ниже коды возврата поддерживаются драйвером SPI, они уникальны для этого драйвера.

ADI_SPI_RESULT_ALREADY_EXCLUSIVE. Эксклюзивный доступ не предоставлен.

ADI_SPI_RESULT_BAD_SLAVE_NUMBER. Ошибочный номер подчиненного устройства (slave number), ожидается значение номера в диапазоне 1 .. 7.

ADI_SPI_RESULT_BAD_TRANSFER_INIT_MODE. Ошибочное значение режима инициализации передачи.

ADI_SPI_RESULT_BAD_WORD_SIZE. Ошибочное значение размера фрейма данных (ожидается 8 или 16).

ADI_SPI_RESULT_BAD_VALUE. Передано ошибочное значение (ожидалось TRUE или FALSE).

ADI_SPI_RESULT_DATAFLOW_ENABLED. Недопустимо, потому что поток данных уже активен.

ADI_SPI_RESULT_NO_VALID_BUFFER. Нет буфера для перемещения данных.

ADI_SPI_RESULT_BAD_BAUD_NUMBER. Ошибочное значение для определения скорости передачи.

[Открытие и конфигурирование драйвера SPI]

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

Точка входа. Когда драйвер устройства SPI открывается вызовом функции adi_dev_Open(), клиент передает в эту функцию параметр, идентифицирующий определенный открываемый драйвер устройства. Этот параметр называется точкой входа (entry point). Для драйвера SPI с управлением по прерыванием точку входа определяет глобальная переменная - структура ADISPIDMAEntryPoint типа ADI_DEV_PDD_ENTRY_POINT, которая находится в исходном коде драйвера SPI (Blackfin\lib\src\drivers\spi\adi_spi.c).

Настройки по умолчанию. В таблице 4 описаны настройки по умолчанию и их возможные значения для драйвера SPI. Если настройки по умолчанию не подходят для имеющейся системы, клиент должен использовать соответствующие идентификаторы команд (command ID), чтобы правильно сконфигурировать драйвер. Настройки, не перечисленные в таблице, не определены по умолчанию.

Таблица 4. Настройки по умолчанию.

Опция По умолчанию Возможные значения Command ID
Режим запуска обмена данными 0x00 (начало обмена осуществляется чтением регистра приема SPI_RDBR) 0x01 (начало обмена осуществляется записью регистра передачи SPI_TDBR) ADI_SPI_CMD_SET_TRANSFER_INIT_MODE
Отправка нулей (Send Zero) 0 (отправлять последнее записанное слово данных) 1 (отправлять нули) ADI_SPI_CMD_SEND_ZEROS
Как быть с необработанными данными (get more data) 0 (отбрасывать новые поступающие данные) 1 (принимать данные, перезаписывая ими старые) ADI_SPI_CMD_SET_GET_MORE_DATA
Разрешение выборки подчиненного устройства 0 (запрет) 1 (разрешение) ADI_SPI_CMD_SET_PSSE
Разрешение MISO 0 (MISO запрещен) 1 (MISO разрешен) ADI_SPI_CMD_SET_MISO
Количество бит фрейма данных 0 (8 бит) 1 (16 бит) ADI_SPI_CMD_SET_WORD_SIZE
Первым передавать младший (LSB) бит 0 (первым передается и принимается старший бит, MSB) 1 (первым передается и принимается младший бит, LSB) ADI_SPI_CMD_SET_LSB_FIRST
Фаза тактов 1 (выборка подчиненного устройства осуществляется программно) 0 (выборка подчиненного устройства осуществляется аппаратно) ADI_SPI_CMD_SET_CLOCK_PHASE
Полярность тактов 0 (для CLK активный уровень лог. 1) 1 (для CLK активный уровень лог. 0) ADI_SPI_CMD_SET_CLOCK_POLARITY
Master/Slave 0 (подчиненное устройство, slave) 1 (главное устройство, master) ADI_SPI_CMD_SET_MASTER
Использовать для выходов master открытый коллектор 0 (обычные, двухтактные выходы) 1 (выходы с открытым коллектором) ADI_SPI_CMD_SET_OPEN_DRAIN_MASTER

Дополнительные необходимые настройки. В дополнение к тем настройкам, которые уже заданы по умолчанию (и которые можно переопределить соответствующими командами драйвера), есть также дополнительные настройки, которые должны быть установлены приложением. Эти настройки перечислены в таблице 5.

Таблица 5. Дополнительные требуемые настройки.

Опция Возможные значения Command ID
Метод потока данных См. выше "Методы поддержки потока данных" ADI_DEV_CMD_SET_DATAFLOW_METHOD

[Что следует учесть при работе с аппаратурой]

Драйвер устройства SPI не требует какой-либо аппаратной конфигурации. По умолчанию функции сигналов SPI работают как GPIO, и сигналы выборки подчиненных устройств мультиплексируются с сигналами других периферийных устройств. Когда функция adi_pdd_Open() открывает этот драйвер, то драйвер установит соответствующие биты PORTx_FER, чтобы сконфигурировать GPIO для использования SPI.

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

[Работа с потоком данных]

Необходимо обратить внимание на выбор версии драйвера SPI, либо версии с поддержкой DMA, либо версии с поддержкой прерываний - в зависимости от требований приложения. Для приложений, которые постоянно только читают большие объемы данных от подключенного через SPI устройства, или только записывают большие объемы данных, лучше подойдет версия драйвера, работающая через DMA. Для приложений, которые периодически читают и/или записывают через SPI небольшие объемы данных, может лучше подойти версия драйвера, работающая по прерываниям.

Что следует учесть для версии драйвера с пользованием DMA: это часто хороший выбор для однонаправленного (симплексного) обмена, когда требуется обмен данными в одном направлении. Например, АЦП постоянно предоставляет данные, и для его подключения к процессору может использоваться SPI с поддержкой драйвера на основе DMA.

Пример кода ниже показывает, как использовать DMA-версию драйвера SPI для организации входящего потока данных в процессор Blackfin. Используется метод пока данных с цепочками буферов и переходом на начало цепочки (chained with loopback dataflow method). С использованием такого метода достаточно предоставить буферы только один раз, и они будут наполняться циклически по кругу снова и снова, без необходимости их повторного предоставления.

#define BUFFER_COUNT (4)
#define DATA_COUNT   (512)
 
static u16                   Data[DATA_COUNT * BUFFER_COUNT];
static ADI_DEV_1D_BUFFER     Buffer[BUFFER_COUNT];
static ADI_DEV_DEVICE_HANDLE SPIHandle;
 
/*********************************************************************
Функция: Callback
Описание: обработка принимаемых данных.
*********************************************************************/
static void Callback(void *AppHandle, u32 Event, void *pArg)
{
   ADI_DEV_1D_BUFFER *pBuffer;
   switch (Event)
   {
   // Событие завершения обработки буфера
   case ADI_DEV_EVENT_BUFFER_PROCESSED:
      pBuffer = pArg;   // избегаем приведения типов
      // Обработка данных, которые были приняты:
      ProcessBuffer(pBuffer);
      break;
   }
}
 
/*********************************************************************
Функция: DriverApp
Описание: открывает, конфигурирует и запускает драйвер
*********************************************************************/
void DriverApp(void)
{
   ADI_DEV_CMD_VALUE_PAIR SPIConfig[] = {
      { ADI_DEV_CMD_SET_DATAFLOW_METHOD, (void *)ADI_DEV_MODE_CHAINED_LOOPBACK }, // цепочка с переходом в начало
      { ADI_SPI_CMD_SET_BAUD_REG,        (void *)0x7ff                         }, // настройка скорости
      { ADI_SPI_CMD_SET_WORD_SIZE,       (void *)16                            }, // размер слова 16 бит
      { ADI_SPI_CMD_SET_MASTER,          (void *)TRUE                          }, // режим master
      { ADI_SPI_CMD_ENABLE_SLAVE_SELECT, (void *)4                             }, // внешнее устройство подключено
                                                                                  // к выборке SS 4
      { ADI_SPI_CMD_SELECT_SLAVE,        (void *)4                             }, // активировать выборку устройства
      { ADI_DEV_CMD_END, NULL }
   };
 
   u32 Result;
   int i;
 
   // Открыть DMA-версию драйвера SPI:
   Result = adi_dev_Open(adi_dev_ManagerHandle,    // хендл ранее инициализированного Менеджера Устройств
                         &ADISPIDMAEntryPoint,     // точка входа в драйвер SPI с поддержкой DMA
                         0,                        // номер SPI0
                         NULL,                     // аргумент для callback-функции
                         &SPIHandle,               // указатель на место хендла DM (для драйвера SPI)
                         ADI_DEV_DIRECTION_INBOUND,// входящие данные
                         DMAHandle,               // хендл Менеджера DMA
                         NULL,                     // Менеджер DCB не используется
                         Callback);                // функция обратного вызова
 
   // Конфигурирование SPI:
   Result = adi_dev_Control(SPIHandle, ADI_DEV_CMD_TABLE, SPIConfig);
 
   // Подготовка буферов:
   for (i = 0; i < BUFFER_COUNT; i++)
   {
      Buffer[i].Data              = &Data[i * DATA_COUNT];
      Buffer[i].ElementCount      = DATA_COUNT;
      Buffer[i].ElementWidth      = 2;
      Buffer[i].CallbackParameter = &Buffer[i];
      Buffer[i].pNext             = &Buffer[i+1];
   }
   Buffer[BUFFER_COUNT-1].pNext = NULL;
 
   // Предоставление буферов драйверу:
   Result = adi_dev_Read(SPIHandle, ADI_DEV_1D, (ADI_DEV_BUFFER *)Buffer);
   // Разрешить поток данных:
   Result = adi_dev_Control(SPIHandle, ADI_DEV_CMD_SET_DATAFLOW, (void *)TRUE);
   // Бесконечный цикл:
   while (1) ;
}

Пример кода ниже показывает, как использовать DMA-версию драйвера SPI для организации исходящего потока данных из процессора Blackfin. Этот пример идентичен предыдущему, разница лишь в том, что вместо функции adi_dev_Read() для предоставления буферов здесь используется функция adi_dev_Write(). Как и в предыдущем примере, здесь используется метод пока данных с цепочками буферов и переходом на начало цепочки (chained with loopback dataflow method). С использованием такого метода достаточно предоставить буферы только один раз, и они будут передаваться циклически по кругу снова и снова, без необходимости их повторного предоставления.

#define BUFFER_COUNT (4)
#define DATA_COUNT   (512)
 
static u16                   Data[DATA_COUNT * BUFFER_COUNT];
static ADI_DEV_1D_BUFFER     Buffer[BUFFER_COUNT];
static ADI_DEV_DEVICE_HANDLE SPIHandle;
 
/*********************************************************************
Функция: Callback
Описание: заполняет буферы передаваемых данных.
*********************************************************************/
static void Callback(void *AppHandle, u32 Event, void *pArg)
{
   ADI_DEV_1D_BUFFER *pBuffer;
   switch (Event)
   {
   // Событие завершения обработки буфера
   case ADI_DEV_EVENT_BUFFER_PROCESSED:
      pBuffer = pArg;   // избегаем приведения типов
      // Заполнение буфера новыми данными:
      FillBuffer(pBuffer);
      break;
   }
}
 
/*********************************************************************
Функция: DriverApp
Описание: открывает, конфигурирует и запускает драйвер
*********************************************************************/
void DriverApp(void)
{
   ADI_DEV_CMD_VALUE_PAIR SPIConfig[] = {
      { ADI_DEV_CMD_SET_DATAFLOW_METHOD, (void *)ADI_DEV_MODE_CHAINED_LOOPBACK }, // цепочка с переходом в начало
      { ADI_SPI_CMD_SET_BAUD_REG,        (void *)0x7ff                         }, // настройка скорости
      { ADI_SPI_CMD_SET_WORD_SIZE,       (void *)16                            }, // размер слова 16 бит
      { ADI_SPI_CMD_SET_MASTER,          (void *)TRUE                          }, // режим master
      { ADI_SPI_CMD_ENABLE_SLAVE_SELECT, (void *)4                             }, // внешнее устройство подключено
                                                                                  // к выборке SS 4
      { ADI_SPI_CMD_SELECT_SLAVE,        (void *)4                             }, // активировать выборку устройства
      { ADI_DEV_CMD_END, NULL }
   };
 
   u32 Result;
   int i;
 
   // Открыть DMA-версию драйвера SPI:
   Result = adi_dev_Open(adi_dev_ManagerHandle,     // хендл ранее инициализированного Менеджера Устройств
                         &ADISPIDMAEntryPoint,      // точка входа в драйвер SPI с поддержкой DMA
                         0,                         // номер SPI0
                         NULL,                      // аргумент для callback-функции
                         &SPIHandle,                // указатель на место хендла DM (для драйвера SPI)
                         ADI_DEV_DIRECTION_OUTBOUND,// исходящие данные
                         DMAHandle,                 // хендл Менеджера DMA
                         NULL,                      // Менеджер DCB не используется
                         Callback);                 // функция обратного вызова
 
   // Конфигурирование SPI:
   Result = adi_dev_Control(SPIHandle, ADI_DEV_CMD_TABLE, SPIConfig);
 
   // Подготовка буферов:
   for (i = 0; i < BUFFER_COUNT; i++)
   {
      Buffer[i].Data              = &Data[i * DATA_COUNT];
      Buffer[i].ElementCount      = DATA_COUNT;
      Buffer[i].ElementWidth      = 2;
      Buffer[i].CallbackParameter = &Buffer[i];
      Buffer[i].pNext             = &Buffer[i+1];
   }
   Buffer[BUFFER_COUNT-1].pNext = NULL;
 
   // Предоставление буферов драйверу:
   Result = adi_dev_Write(SPIHandle, ADI_DEV_1D, (ADI_DEV_BUFFER *)Buffer);
   // Разрешить поток данных:
   Result = adi_dev_Control(SPIHandle, ADI_DEV_CMD_SET_DATAFLOW, (void *)TRUE);
   // Бесконечный цикл:
   while (1) ;
}

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

1. После вызова adi_dev_Open драйвер настраивает ножки выбранного SPI под использование аппаратурой SPI. Поэтому если Вы думаете программно управлять выборкой SPI (как GPIO), и подключили выборку к сигналу ~SPIxSEL1, то для программного управления этим выводом после инициализации драйвера требуется соответствующая настройка регистра PORTnIO_FER. Пример:

// Открытие драйвера SPI1 на передачу с использованием DMA
Result = adi_dev_Open(
            adi_dev_ManagerHandle,     // Хендл Менеджера Устройств
            &ADISPIDMAEntryPoint,      // Точка входа драйвера SPI с поддержкой DMA
            1,                         // Номер устройства: используется SPI1
            NULL,                      // Аргумент для callback-функции
            &DevHandleSPI1,            // Указатель на место хендла DM (для драйвера SPI)
            ADI_DEV_DIRECTION_OUTBOUND,
            adi_dma_ManagerHandle,     // Хендл Менеджера DMA
            NULL,                      // Менеджер DCB не используется
            spiCallbackFunctionTX      // Callback-функция
);
if (Result!=ADI_DEV_RESULT_SUCCESS)
{
   VDK::KernelPanic(VDK::kThreadError, (VDK::SystemError)errSPI1_dev_Open, Result);
}
Result = adi_dev_Control( DevHandleSPI1, ADI_DEV_CMD_TABLE, (void*)SPIConfig );
if (Result!=ADI_DEV_RESULT_SUCCESS)
{
   VDK::KernelPanic(VDK::kThreadError, (VDK::SystemError)errSPI1_dev_Control1, Result);
}
//Настройка ножки ~SPI1SEL1 как выход под управление GPIO:
*pPORTDIO_FER |= PD4;
*pPORTDIO_DIR |= PD4;

2. Драйвер SPI, работающий через DMA, будет вызывать функцию callback только в том случае, если функцией adi_dev_Read или adi_dev_Write предоставлена цепочка из не менее чем двух буферов ADI_DEV_1D_BUFFER. Тогда callback будет срабатывать на обработку каждого буфера. Если же предоставлена цепочка буферов, в которой находится только 1 буфер, то функция callback по окончании обработки буфера вызвана не будет.

Таким образом, если Вы намерены использовать блокировку на семафоре в ожидании окончания передачи блока данных (в callback семафор должен выбираться, чтобы ожидающий поток семафор разблокировался), то это можно реализовать только для блоков, у которых размер больше 1 элементарной посылки (u8 или u16, в зависимости от настройки SPI). Если нужно передать через DMA данные из одной элементарной посылки, то блокировку и callback использовать нельзя.

Ниже приведен пример кода, который передает через DMA блок данных произвольной длины байтовыми посылками (u8). Если в блок размером в 1 байт, то создается цепочка из одного буфера ADI_DEV_1D_BUFFER. Если же блок больше 1 байта (2 байта и больше), то вся передача распределяется на 2 буфера, и используетс блокировка потока в расчете на срабатывание callback.

static ADI_DEV_1D_BUFFER BufferSPI1[2];
 
void blackfin_hw_spi_write_DMA (uint8_t *data, uint32_t datalen)
{
   static bool dataflow_enabled = false;
   u32 Result;
 
   if (0==datalen)
      return;
   
   BufferSPI1[0].Data              = data;
   BufferSPI1[0].ElementWidth      = 1;
   BufferSPI1[0].CallbackParameter = &BufferSPI1[0];
   if (1==datalen)
   {
      //Callback и блокировку использовать нельзя,
      // настраивается цепочка из 1 буфера:
      BufferSPI1[0].ElementCount      = datalen;
      BufferSPI1[0].pNext             = NULL;
   }
   else
   {
      //Используется Callback и блокировка потока на семафоре semaph 
      // в ожидании окончания передачи буфера:
      BufferSPI1[0].ElementCount      = datalen-1;
      BufferSPI1[0].pNext             = &BufferSPI1[1];
      BufferSPI1[1].Data              = &data[datalen-1];
      BufferSPI1[1].ElementCount      = 1;
      BufferSPI1[1].ElementWidth      = 1;
      BufferSPI1[1].CallbackParameter = &BufferSPI1[1];
      BufferSPI1[1].pNext             = NULL;
   }
   Result = adi_dev_Write(DevHandleSPI1, ADI_DEV_1D, (ADI_DEV_BUFFER *)BufferSPI1);
   if (Result!=ADI_DEV_RESULT_SUCCESS)
   {
      VDK::KernelPanic(VDK::kThreadError, (VDK::SystemError)errSPI1_dev_Write, Result);
   }
   if (!dataflow_enabled)
   {
      // Если поток данных еще не разрешен, разрешаем его:
      Result = adi_dev_Control(DevHandleSPI1, ADI_DEV_CMD_SET_DATAFLOW, (void *)TRUE);
      if (Result!=ADI_DEV_RESULT_SUCCESS)
      {
         VDK::KernelPanic(VDK::kThreadError, (VDK::SystemError)errSPI1_dev_Control2, Result);
      }
      dataflow_enabled = true;
   }
   if (1<datalen)
   {
      //Блокировка на ожидании окончания передачи:
      VDK::PendSemaphore(semaph, 0);
      delay_ms(1);
   }
}
 
static void spiCallbackFunctionTX(void *AppHandle, u32 Event, void *pArg)
{
   ADI_DEV_1D_BUFFER *pBuffer;
   switch (Event)
   {
   // Событие завершения обработки буфера
   case ADI_DEV_EVENT_BUFFER_PROCESSED:
      pBuffer = (ADI_DEV_1D_BUFFER*)pArg;
      if (pBuffer == &BufferSPI1[1])
      {
         //Данные переданы, выставление семафора для
         // разблокировки потока:
         VDK::C_ISR_PostSemaphore(semaph);
      }
      break;
   default:
      // Ошибка: вызвать исключение пользователя
      VDK::KernelPanic(VDK::kThreadError,
                       (VDK::SystemError)errSPI1_dev_callback,
                       Event);
      break;
   }
}

3. Недопустимо предоставлять цепочку буферов, где указано 0 элементов (поле буфера ElementCount) - поведение драйвера будет непредсказуемым.

4. Что делать, если Вы столкнулись с неожиданным поведением кода, использующего драйвер? Например, все API-функции adi_dev_xxx возвращают успешный результат, но драйвер не работает, либо программа ведет себя неустойчиво (срабатывает KernelPanic, блокируются потоки и происходят другие странные вещи).

Если это так, что определите 2 макроопределения ADI_DEV_DEBUG и ADI_SSL_DEBUG. Это подключит дополнительный код проверки ошибок, который кодами возврата из функций укажет Вам на возможные проблемы в использовании драйвера.

5. Для облегчения отладки сделайте копии файлов adi_spi.c, adi_dma.c, adi_dev.c в корневом каталоге проекта, и добавьте их в проект. Тогда станет доступна отладка по исходному коду драйверов.

[Ссылки]

1VDK: менеджер прерываний.
2VDK: драйверы устройств и системные службы процессоров Blackfin.
3Интерфейс SPI.
4ADSP-BF538: интерфейс SPI.
5. VDK: менеджер драйверов устройств.
6. Драйвер SPI для Blackfin, работающий по прерываниям.