Введение в библиотеку SPI для Arduino с АЦП LTC1286 и ЦАП DAC714

Добавлено 11 марта 2017 в 12:35

Введение в библиотеку SPI для Arduino с примером скетча для 12-битного аналого-цифрового преобразователя LTC1286 и 16-битного цифро-аналогового преобразователя DAC714.

Об SPI

Последовательный периферийный интерфейс, более известный как SPI, был создан компанией Motorola для передачи данных между микроконтроллерами и периферийными устройствами, используя меньшее количество выводов по сравнению с параллельной шиной. SPI может использоваться для сопряжения любых возможных периферийных устройств, таких как датчики, сенсорные экраны и IMU датчики. SPI может даже использоваться для связи одного микроконтроллера с другим или для связи с физическими интерфейсами Ethernet, USB, USART, CAN и WiFi модулей. SPI чаще всего реализуется, как четырехпроводная шина с линиями для тактового сигнала, входа данных, выхода данных и выбора периферийного устройства. Линии тактового сигнала и данных используются всеми периферийными (или ведомыми) устройствами на шине, а вывод выбора ведомого служит для идентификации каждого подключенного периферийного устройства.

Все шины SPI должны содержать одно ведущее устройство (master) и одно или несколько ведомых устройств (slave). Некоторые устройства, такие как DAC714, используют дополнительные линии управления. В случае с DAC714 эта линия используется для очистки двойного буфера, позволяя использовать до трех микросхем DAC714 на одной пятипроводной шине, устраняя необходимость в дополнительной линии управления выборов ведомого устройства. Основными ограничивающими характеристиками шины SPI являются полоса пропускания и количество доступных выводов выбора ведомого устройства. Для микроконтроллеров в больших корпусах с тактовой частотой 20 МГц и выше и с сотнями выводов GPIO это вряд ли является ограничением.

Схема подключения устройств к шине SPI
Схема подключения устройств к шине SPI

Также об основных особенностях SPI можно прочитать в статье «Назад к основам: SPI (последовательный периферийный интерфейс)».

Реализация SPI

Существует два основных способа реализации связи через SPI на Arduino или любом микроконтроллере. Первый и более распространенный метод – это аппаратный контроллер SPI. Arduino поставляется с библиотекой SPI для взаимодействия с аппаратным контроллером SPI, поэтому мы будем использовать эту библиотеку в наших примерах. Другой метод – программный SPI или "побитовое управление". Побитовое управление включает в себя ручное определение всех аспектов связи через SPI программно и может быть реализовано на любом выводе, в то время как аппаратный SPI должен быть подключен к конкретным SPI выводам микроконтроллера. Программный SPI намного медленнее, чем аппаратный SPI, и может использовать на себя ценную память программ и ресурсы процессора. Однако, в некоторых случаях, когда требуется несколько шин SPI на одном микроконтроллере, или при отладке нового SPI интерфейса побитовое управление может быть очень полезным.

Когда у ведомого устройства вывод выбора ведомого устройства принимает состояние низкого логического уровня, значит, это ведомое устройство пытается отправить данные по шине SPI ведущему устройству или принять данные. Когда у ведомого устройства вывод выбора ведомого устройства находится в состоянии логической единицы, это ведомое устройство игнорирует ведущее устройство, что позволяет нескольким ведомым устройствам совместно использовать одни и те же линии данных и тактового сигнала. Для передачи данных от ведомого устройства к ведущему служит линия MISO (Master In Slave Out, вход ведущего – выход ведомого), иногда называемая SDI (Serial Data In, вход последовательных данных). Для передачи данных от ведущего устройства к периферии используется линия MOSI (Master Out Slave In, выход ведущего – вход ведомого), также известная как SDO (Serial Data Out, выход последовательных данных). И наконец, тактовые импульсы от ведущего устройства SPI идут по линии, обычно называемой SCK (Serial Clock, последовательный тактовый сигнал) или SDC (Serial Data Clock, тактовый сигнал последовательных данных). В документации на Arduino предпочтение отдается названиям MISO, MOSI и SCK, поэтому мы будем использовать их.

Начало работы

Прежде чем приступить к написанию нового кода для периферийных устройств SPI, крайне важно обратить внимание на пару элементов в техническом описании новых компонентов. Во-первых, мы должны учитывать полярность и фазу тактового сигнала по отношению к данным. Они различаются у разных устройств и разных производителей. Полярность тактового сигнала может быть высокой или низкой и, как правило, называется CPOL (Clock POLarity). Когда CPOL = 0, логическая единица указывает на тактовый цикл, и когда CPOL = 1, логический ноль указывает на тактовый цикл. Фаза тактового сигнала, обычно называемая CPHA, указывает, когда данные захватываются и продвигаются в сдвиговом регистре относительно тактового сигнала. Для CPHA = 0 данные захватываются при нарастающем фронте тактового импульса, а продвигаются при спадающем фронте; а для CPHA = 1 – наоборот. Комбинация полярности и фазы тактового сигнала дает четыре отдельных режима данных SPI. SPI режим 0: CPOL и CPHA равны 0. SPI режим 1: CPOL = 0, а CPHA = 1. SPI режим 2: CPOL = 1, а CPHA = 0. И наконец, SPI режим 3: я уверен, вы сможете догадаться, в каких состояниях будут CPOL и CPHA.

Режимы работы SPI
Режимы работы SPI

Некоторые технические описания не испльзуют названия CPOL и CPHA, разработанные Freescale. Чтобы помочь понять режимы SPI, LTC1286 использует SPI режим 2. Взгляд на временную диаграмму в техническом описании поможет вам ознакомиться с SPI режимами данных. Для справки, DAC714 использует SPI режим 0 (спецификации DAC714 и LTC1286 приведены ниже). Далее нам нужно определить, как периферийное устройство сдвигает биты. Существует два возможных варианта: MSB или LSB – идет первым старший или младший значащий бит, и установить порядок функцией setBitOrder(). Наконец, нам нужно определить, какую тактовую частоту наше устройство может принять, и на какой тактовой частоте работает аппаратный SPI на нашей плате Arduino. В случае с Arduino Mega и платами с тактовой частотой 16 МГц по умолчанию значение тактового сигнала шины SPI составляет 4 МГц. Библиотека SPI для Arduino позволяет разделить тактовую частоту на 2, 4, 8, 16, 32, 64 и 128.

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

Библиотека SPI для Arduino за раз передает и принимает один байт (8 бит). Как мы увидим в примерах два и три, для этого требуется выполнить некоторые манипуляции с переданными и полученными байтами. Выводы аппаратного SPI на платах Arduino используются для разъема ICSP, на всех платах Arduino MOSI находится на выводе 4 ICSP разъема, MISO – на выводе 1, а SCK – на выводе 3. Если Arduino является ведущим устройством на шине SPI, любой ее вывод может использоваться в качестве вывода выбора ведомого устройства. Если Arduino является ведомым устройством на шине SPI, то для выбора ведомого должен использоваться вывод 10 на платах Uno и Duemilanove и вывод 53 на платах Mega 1280 и 2560.

Мы сфокусируемся на следующих функциях библиотеки SPI для Arduino:

  • SPISettings()
  • begin()
  • end()
  • beginTransaction()
  • endTransaction()
  • setBitOrder()
  • setClockDivider()
  • setDataMode()
  • transfer()

Пример 1

В примере датчика давления BarometricPressureSensor SCP1000 требует записи конкретных значений в определенные регистры для настройки SCP1000 для работы с низким уровнем шума. Этот скетч также содержит определенную команду для чтения и записи в SCP1000. Это самый важный шак во взаимодействии с периферийными SPI устройствами и требует внимательно изучения технического описания и временной диаграммы.

const byte READ = 0b11111100;    // команда чтения SCP1000
const byte WRITE = 0b00000010;   // команда записи SCP1000

// Настройка SCP1000 для работы с низким уровнем шума:
writeRegister(0x02, 0x2D);
writeRegister(0x01, 0x03);
writeRegister(0x03, 0x02);
// дать датчику время для настройки:
delay(100);

Пример 2

В примере 2 демонстрируется прием данных от 12-разрядного АЦП с помощью библиотеки SPI для Arduino. В качестве подопытного АЦП используется LTC1286. 1286 – это хорошо известный АЦП, который существует на рынке очень долгое время и имеет несколько аналогов. 1286 – это дифференциальный АЦП последовательного приближения, доступный в 8-выводном DIP корпусе, что делает его удобным для макетирования и прототипирования. Способ, которым мы получаем данные от LTC1286, также приведет к редкому сценарию, в котором побитовое управление менее сложно, чем использование библиотеки SPI для Arduino. Прикрепленное описание LTC1286 содержит временную диаграмму передачи данных, которая очень полезна для понимания кода. 1286 не требует настройки, а только передает данные. Это делает реализацию связи с 1286 на Arduino очень простой.

Однако, сложная часть заключается в том, как библиотека SPI будет интерпретировать то, что получила. Вызов SPI.transfer() обычно передает команду по каналу SPI и прослушивает его на предмет получения данных. В этом случае мы ничего не передаем: SPI.transfer(0). Функция transfer принимает первый байт данных и присвает его byte_0. Первый байт данных включает все принятые данные в то время, когда на CS (выбор ведомого) был низкий логический уровень. Он включает в себя два бита данных HI-Z, когда АЦП производит выборку аналогового напряжения для преобразования, и нулевой бит, указывающий начало пакета. Это означает, что наш первый байт будет содержать только пять полезных битов. Сразу после нашего первого вызова SPI.transfer(0), мы вызываем эту функцию снова и на этот раз присваиваем ее результат переменной byte_1. byte_1 будет содержать 8 бит данных, но нам интересны только семь из них. седьмой бид будет обычно совпадать с шестому, и его можно не учитывать, так как эффективное количество бит составляет только одиннадцать из двенадцати. По этой причине справедливо рассматривать LTC1286 как 11-разрядный АЦП. После отброса ненужных битов восстанавливается аналоговое значение.

Временая диаграмма получения данных от АЦП LTC1286 через шину SPI
Временная диаграмма получения данных от АЦП LTC1286 через шину SPI
Схема подключения аналого-цифрового преобразователя LTC1286
Схема подключения аналого-цифрового преобразователя LTC1286
//    SPI выводы
//    SS    вывод 48
//    MISO  вывод 50
//    SCK   вывод 52

#include <SPI.h>

const int spi_ss = 48;  // вывод выбора ведомого SPI
uint8_t byte_0, byte_1; // Первый и второй байты для чтения
uint16_t spi_bytes;     // Окончательное 12-разрядное сдвинутое значение
float v_out;            // Напряжение с десятичной запятой
float vref = 5.0;       // Опорное напряжение на выводе Vref


void setup() 
{
  Serial.begin(9600);          // Инициализировать последовательный порт и установить скорость
  pinMode(spi_ss, OUTPUT);     // Установить SPI вывод выбора ведомого на выход
  digitalWrite(spi_ss, HIGH);  // Убедиться, что на spi_ss установлена логическая единица 
  SPI.begin();                 // begin SPI
}

void loop() 
{
  // установить скорость, формат и полярность тактового сигнала/данных во время инициации SPI
  SPI.beginTransaction(SPISettings(1000, MSBFIRST, SPI_MODE2)); 
  // установить CS вывод LTC в низкий уровень для инициации выборки АЦП и передачи данных
  digitalWrite(spi_ss, LOW);

  byte_0 = SPI.transfer(0); // read firt 8 bits
  byte_1 = SPI.transfer(0); // read second 8 bits

  // установить CS вывод LTC в высокий уровень, чтобы остановить LTC от передачи нулей
  digitalWrite(spi_ss, HIGH);
  
  // закрыть SPI соединение  
  SPI.endTransaction();
  
  // & B000 сбрасывает первые 3 бита  (два HI-Z бита и один нулевой бит) и сдвинуть в spi_bytes
  // затем мы добавляем оставшийся байт, сдвинутый вправо для удаления бита 12
  spi_bytes = ( ( (byte_0 & B00011111) <<7) + (byte_1 >>1) );
  
  // и наконец, мы преобразуем значение в вольты. 1LSB = vref/2048
  v_out = vref * (float(spi_bytes) / 2048.0);

  Serial.println(v_out, 3);
  delay(250);
}

Пример 3

Мы увидели, как получать данные по SPI, теперь пришло время рассмотреть, как отправлять данные. В примере 3 рассматривается, как общаться с микросхемой с наследием, аналогичным LTC1286, но с совершенно противоположной функциональностью. DAC714 – это 16-разрядный цифро-аналоговый преобразователь. У DAC714 имеется дополнительный вывод связи, который включает дополнительную защелку данных. Это позволяет включать DAC714 последовательно с другими DAC714 (до двух штук) без дополнительной линии выбора ведомого. Двойной буфер DAC714 позволяет загружать два значения в DAC714 за каждый цикл. Временная диаграмма DAC714 приведена в техническом описании.

Схема подключения цифро-аналогового преобразователя DAC714
Схема подключения цифро-аналогового преобразователя DAC714
//    Выводы SPI 
//    SS    вывод 48
//    MOSI  вывод 51
//    MISO  вывод 50
//    SCK   вывод 52
//    latch вывод 46

#include <SPI.h>

const int spi_ss = 48;  // регистр сдвига A0 DAC714P
const int dac_lch = 46; // защелка ЦАП A1 DAC714
uint16_t input_0, input_1; // входные 16-битные значения 
uint8_t byte_0, byte_1, byte_2, byte_3; // байты для передачи SPI

void setup() 
{
  Serial.begin(9600);
  pinMode(spi_ss, OUTPUT);
  pinMode(dac_lch, OUTPUT);
  digitalWrite(spi_ss, HIGH);
  digitalWrite(dac_lch, HIGH);
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.begin();
}

void loop() 
{
  static uint16_t count = 0;
  input_0 = count;
  input_1 = -count;
  count += 1;
  Serial.println(input_0);
  Serial.println(input_1);
  //
  digitalWrite(spi_ss, LOW); // A0
  byte_0 = (input_1 >> 8);
  byte_1 = (input_1 & 0xFF); 
  byte_2 = (input_0 >> 8);
  byte_3 = (input_0 & 0xFF);

  SPI.transfer(byte_0);
  SPI.transfer(byte_1);
  SPI.transfer(byte_2);
  SPI.transfer(byte_3);
  digitalWrite(spi_ss, HIGH);

  digitalWrite(dac_lch, LOW);
  digitalWrite(dac_lch, HIGH);

  delay(3);
}

Мы указали параметры SPI с помщью setDataMode(), setBitOrder() и setClockDivider() в void setup(), вместо использования SPI.beginTransaction(), просто, чтобы продемонстрировать еще один способо настройки. Снова испльзуется функция SPI.transfer(), но на этот раз нам неинтересен прием данных. Два 16-разрядных целых числа преобразуются в четыре байта для передачи через функцию SPI.transfer(). Сначала мы закружаем второе входное целое число, input_1, первым потому, что оно будет зафиксировано и загружено после преобразования input_0. Также обратите внимание, что делитель дает тактовую частоту, вероятно, намного медленную, чем максимальный тактовый сигнал, который DAC714 может принять.

Вот и всё! Надеюсь, статья оказалась полезной. Оставляйте комментарии!


На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.


Сообщить об ошибке