Arduino:Библиотеки/OneWire

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.



Библиотека OneWire[1]

Библиотека OneWire в настоящий момент поддерживается Полом Стоффрегеном (Paul Stoffregen). Если вы нашли баг или разработали улучшение, можете связаться с ним по почте paul@pjrc.com. Кроме того, убедитесь, что используете самую последнюю версию библиотеки.

Протокол 1-Wire

Протокол 1-Wire – это протокол, используемый для управления устройствами, которые производятся компанией Dallas Semiconductor (ныне Maxim). Хотя 1-Wire является приприетарным протоколом и торговой маркой Dallas, программисты, использующие драйверы 1-Wire, не обязаны делать за это никаких выплат.

Сеть 1-Wire, которую Dallas называет MicroLan (торговая марка), состоит из одного ведущего устройства, к которому при помощи единственной линии передачи данных подключено одно или несколько ведомых устройств. Эту линию, помимо прочего, можно использовать для электропитания ведомых устройств (ситуацию, когда устройства подпитываются через шину 1-Wire, называют «паразитным питанием»).

Более подробно о 1-Wire можно почитать в руководствах (этом и этом) Тома Бойда.

Среди прочих устройств, работающих через протокол 1-Wire, особенно популярны температурные датчики – они недороги, просты в использовании и позволяют напрямую считывать откалиброванные цифровые температурные данные. Кроме того, они терпимы к длинным проводам, которыми часто приходится пользоваться при создании цепи с Arduino. Один из примеров ниже демонстрирует, как работать с 1-Wire на примере цифрового термометра DS18S20. Многие чипы 1-Wire могут работать и через паразитное, и через нормальное питание.

Интерфейсы 1-Wire

Управление шиной при помощи специальных устройств

Шина 1-Wire может управляться посредством специальных устройств – их называют «хозяевами шины» или «контроллерами шины». Такие устройства производит, к примеру, та же Dallas, а также ряд других компаний-производителей. Большинство «хозяев шин» перечислены здесь.

Эти устройства специально созданы и оптимизированы для того, чтобы максимально эффективно осуществлять операции записи/считывания с устройствами типа 1-Wire. Будучи похожими на UART/USART, они управляют тактовыми операциями при помощи буфера, тем самым увеличивая точность и высвобождая вычислительные ресурсы главного процессора (к примеру, микроконтроллера). Внешние подтягивающие резисторы тоже не требуются.

Многие из этих чипов осуществляют также исправление ошибок, которые возникают при использовании шин данных (к примеру, потери целостности сигнала, колебания уровня сигнала, отражений и т.д.) и способны повлечь различные проблемы, особенно с крупными сетями. Кроме того, многие из этих девайсов обладают дополнительными функциями и могут работать с большим количеством интерфейсов. Их цена варьируется от 1 до 30 долларов.

Другим важным преимуществом является поддержка owfs – это файловая система для считывания/записи, совместимая с большим количеством устройств для управления шиной 1-Wire.

Управление шиной при помощи UART/USART

Большинство UART/USART идеально подходят для того, чтобы работать со скоростями свыше 15,4 Кбит/сек – скорости, требуемой для стандартного режима шины 1-Wire. Но важнее то, что частота и буфферизация управляются ими раздельно, что, опять же, позволяет разгрузить главный процессор микроконтроллера. Более подробно о реализации этого метода можно почитать тут.

Управление шиной при помощи технологии Bit-Banging

Если управление буфферизацией/частотой при помощи контроллера или UART\USART по какой-то причине невозможно, шину 1-Wire можно настроить на GPIO-контакте. То есть это, по сути, программная эмуляция UART\USART с ручным переключением состояния контакта и реконструкцией сигнала от присланных данных. Однако поскольку это программный процесс, на него будут напрямую влиять другие системные процессы, выполняемые в процессоре, что может негативно сказаться на его работе.

На Arduino и прочих совместимых чипах это можно выполнить как раз при помощи библиотеки OneWire – она позволяет реализовать эту технологию на любом цифровом контакте.

На одноплатных компьютерах вроде Raspberry Pi имеется встроенная поддержка протокола 1-Wire, реализованная при помощи драйверов ядра. В частности, очень популярны w1-gpio, w1-gpio-therm и w1-gpio-custom, которые включены в большинство последних дистрибутивов Raspbian. С их помощью можно настроить коммуникацию с устройствами типа 1-Wire, не используя при этом никакого дополнительного оборудования. На данный момент, однако, поддерживается ограниченное количество устройств, а на программном уровне есть ограничение на размер шины.

Питание устройств типа 1-Wire

Питание чипа можно обеспечить двумя способами. Первый (его называют «паразитическим» питанием) – это когда к чипу подключено всего два провода. Второй – это когда к этим двум проводам добавляется еще один, через который, собственно, и осуществляется питание. В ряде случаев второй способ более надежен. Однако новичкам (особенно в ситуациях, когда расстояние между Arduino и чипом составляет не более 6 метров) лучше начать с использования паразитического питания. Код ниже работает с обоими способами.

Паразитическое питание

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

Со стороны ведущего устройства к шине 1-Wire должен быть подключен подтягивающий резистор на 4,7 кОм. Когда эта линия будет переключена в состояние HIGH, устройство начнет тянуть ток, чтобы зарядить внутренний конденсатор.

Сила тока, передаваемого таким способом, как правило, невелика, но при температурной конверсии или записи на EEPROM может достигать 1,5 миллиампер. Когда ведомое устройство выполняет одну из этих операций, контроллер шины должен держать шину в состоянии HIGH, пока эта операция не будет выполнена. При температурной конверсии для DS12S20 требуется задержка в 750 миллисекунд, причем контроллер в течение этого времени не может делать ничего – ни давать команды другим устройствам, ни опрашивать ведомое устройство о выполнении команды. В библиотеку OneWire тоже встроена поддержка этой особенности – она позволяет держать шину в состоянии HIGH после записи данных.

Обычное питание (с использованием внешнего источника)

Если вы хотите запитать чип от внешнего источника, для этого потребуется три провода: один для передачи данных, один для заземления и один для, собственно, питания. Для этого способа по-прежнему нужен подтягивающий резистор на 4,7 кОм, подключенный к шине 1-Wire. Поскольку теперь шина свободна для передачи данных, микроконтроллер может постоянно опрашивать девайс на предмет того, как происходит процесс конверсии. Таким образом, запрос на конверсию можно выполнить сразу после того, как устройство отчитается о выполненной операции – в отличие от паразитического питания, когда вам приходится ждать, пока будет выполнена конверсия (продолжительность этой задержки зависит от функции устройства и его разрешения).

Примечание по резисторам

Согласно даташиту ATmega328/168, для этого микроконтроллера нужны резисторы от 1,6 кОм, однако несколько пользователей обнаружили, что для больших сетей лучше использовать резисторы меньшего номинала.

Другими словами, для больших сетей имеет смысл попробовать резисторы поменьше.

Обращение к устройствам типа 1-Wire

Каждое устройства типа 1-Wire обладает уникальным 64-битным ROM-адресом, который состоит из 8-битного кода, обозначающего семейство, 48-битного серийного кода и 8-битного CRC. CRC используется для проверки целостности данных. К примеру, код ниже проверяет, является ли устройство, к которому обращается скетч, температурным датчиком DS18S20 – путем проверки кода, обозначающего семейство, т.е. «0x10». Чтобы использовать этот скетч с более новым DS18B20, вам нужно будет указать проверку по коду «0x28», а если с DS1822, то по коду «0x22».

Команды к одному устройству

Перед тем, как отправлять команду к одному устройству, ведущее устройство должно выбрать адресата при помощи его уникального ROM-адреса. Если это устройство будет обнаружено, то все последующие команды тоже будут отправлены к нему.

Команды к нескольким устройствам

Вы также можете отправить команду сразу всем ведомым устройствам, воспользовавшись командой 0xCC (она означает «Skip ROM» или «пропустить ROM-адрес»). Впрочем, перед использованием важно знать, какие эффекты это может повлечь. Иногда это очень удобный метод – к примеру, если дать команду 0xCC, а следом за нею 0x44 (она означает «Convert T» или «выполнить температурную конверсию»), то все устройства в сети начнут выполнять, собственно, температурную конверсию. То есть в данном случае использование 0xCC может сберечь вам немного времени. С другой стороны, если воспользоваться командой 0xBE (она означает «Read Scratchpad» или «прочитать данные из оперативной памяти»), то все девайсы в сети начнут одновременно передавать эти данные ведущему устройству. Также при использовании «Skip ROM» нужно учитывать энергопотребление всех устройств, подключенных к сети (это актуально, к примеру, в случае температурной конверсии).

Более подробную информацию читайте, пожалуйста, в даташитах DS18S20 и DS18B20.

Считывание данных с устройства типа 1-Wire

Чтобы прочесть данные с устройства типа 1-Wire, нужно выполнить несколько шагов. Каких именно шагов – зависит от самого устройства, т.к. разные устройства могут отсылать разные значения. Популярный DS18B20, к примеру, считывает данные о температуре, а DS2438 – о напряжении, силе тока и температуре.

Два основных этапа при считывании данных

Конверсия

Эта команда шлется устройство, чтобы оно начало выполнять операцию конверсии. В случае DS18B20 этой командой будет байт 0x44 (команда «Convert T»). В библиотеке OneWire за это отвечает функция ds.write(0x44), где ds – это экземпляр класса OneWire. После получения этой команды устройство начинает считывать данные с внутреннего АЦП, а затем копирует их в регистры оперативной (scratchpad) памяти.

Длительность процесса конверсии варьируется в зависимости от разрешения и указана в даташите устройства. Согласно даташиту, у датчика DS18B20 конверсия температуры может занимать от 94 (разрешение 9 бит) до 750 (разрешение 12 бит) миллисекунд. Во время выполнения конверсии девайс можно опрашивать, то есть использовать, к примеру, функцию ds.read(), чтобы проверить успешность выполнения конверсии.

Чтение оперативной (scratchpad) памяти

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

Синхронные и асинхронные запись/считывание

Большинство программ и скетчей для девайсов типа 1-Wire (в особенности те, что написаны для Arduino) используют простой алгоритм «конверсия, ожидание, считывание» – даже при использовании нескольких устройств. Это может стать причиной ряда проблем:

Синхронизация с другими функциями

Если в скетче жестко прописано время ожидания, то ведущее устройство во время конверсии должно просто остановиться и ждать (за исключением случаев, когда используется многопоточность). Это влечет серьезную проблему и в том случае, если в скетче есть другие процессы, привязанные к времени, и в том случае, если их нет – многим программам нужно ждать ввода данных, обработки данных и выполнения многих других операций, которые не терпят задержек, необходимых для конверсии температурных данных. Как упоминалось выше, 12-битная конверсия в DS18B20 требует задержки в 750 миллисекунд. Поэтому в использовании ожидания, пока датчик выполнит процесс конверсии, никакого смысла нет. Гораздо разумнее будет послать команду конверсии, а за ее результатом вернуться позже, когда конверсия уже будет завершена – прочитав этот результат при помощи команды «Read Scratchpad».

Скорость опроса нескольких устройств

Еще одна проблема метода «конверсия, ожидание, считывание» – время, затрачиваемое на считывание данных с нескольких устройств. То есть, если ничего не менять, то эти три действия будут выполняться для каждого устройства по отдельности. Но гораздо эффективней было бы сначала отправить запрос на конверсию (или всем устройствам сразу, или по отдельности), затем сделать одну единственную задержку (сразу для всех), а потом по очереди считывать данные с каждого из этих устройств. Более подробно читайте тут.

Подгонка времени ожидания ко времени конверсии

Самый эффективный и быстрый способ считывания информации с девайса типа 1-Wire – это учитывать еще и время конверсии, что можно делать при помощи функции ds.read(). К примеру, в одном из скетчей ниже указана в задержка в 1000 миллисекунд, тогда как в даташите максимальным временем конверсии указано 750 миллисекунд, а фактически это происходит за 625 миллисекунд или меньше. Но важнее то, что это значение должно соответствовать разрешению, с которым происходит конверсия. Конверсия с разрешением 9 бит, например, занимает до 94 миллисекунд, поэтому ожидание в течение 1000 миллисекунд просто не имеет смысла.

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

Обсуждение и примеры кода, затрагивающие эту тему, можно посмотреть здесь.

История

Оригинальная версия OneWire была разработана в 2007 году Джимом Стадтом (Jim Studt), и ее целью было упростить работу с девайсами типа 1-Wire. Эволюцию библиотеки можно наблюдать в этом форумном посте. Первая версия OneWire работала только с arduino-0007 и требовала большой (256-байтной) таблицы поиска для выполнения CRC-вычислений. Позже библиотека была обновлена и получила совместимость с arduino-0008 и более поздними релизами. В самой последней версии таблицы поиска для CRC-вычислений больше нет, и эта версия тестировалась только для arduino-0010 и ниже.

В библиотеке OneWire есть баг, из-за которого при использовании функции поиска запускается зацикливание. О том, как его исправить, можно прочесть в этом посте – разные способы можно найти в ответах #3, #17, #24, #27.

В версии 2.0 объединены улучшенная функция поиска от Робина Джеймса (Robin James) и улучшенные I/O функции Пола Стоффрегена (исправлены случайные ошибки подключения). Кроме того, добавлено несколько небольших оптимизаций.

Версия 2.1 добавляет совместимость с бетой Arduino 1.0 и chipKit (Джейсон Дэнжел), вспомогательные функции, CRC16, а также улучшенный температурный пример (Пол Стоффреген), пример для считывания с PROM-чипа DS250x (Гильермо Ловато) и пример для работы с PIO-чипом DS2408(Гленн Тревитт).

Функции

  • OneWire myWire(pin)
    
    При помощи указанного контакта создает объект OneWire. Хотя к одному контакту можно подключить сразу несколько устройств, зачастую это трудновыполнимо. Поэтому, если вы используете несколько устройств, подключайте их группками не к одному, а к нескольким контактам. Вы можете создать несколько объектов OneWire – по одному на каждый контакт.
  • myWire.search(addrArray)
    
    Ищет следующее устройство. Массив addrArray – это массив из 8 байтов. Если устройство обнаружено, addrArray заполняется адресом этого устройства, после чего функция возвращает true. Если больше устройств не найдено, функция возвращает false.
  • myWire.reset_search()
    
    Начинает новый поиск. Следующее использование поиска начнется на первом устройстве.
  • myWire.reset()
    
    Делает сброс шины 1-Wire. Как правило, это нужно перед коммуникацией с каким-либо устройством.
  • myWire.select(addrArray)
    
    Выбирает устройство, чей адрес указан в скобках. Нужна после сброса – чтобы выбрать устройство, которое нужно использовать. Последующая коммуникация будет именно с этим устройством, но до нового сброса.
  • myWire.skip()
    
    Пропускает выбор устройства. Это работает лишь тогда, когда подключено лишь одно устройство, то есть вы можете не использовать поиск, а сразу получить доступ к своему устройству.
  • myWire.write(num)
    
    Записывает байт.
  • myWire.write(num, 1)
    
    Записывает байт и оставляет включенным паразитное питание, подключенное к шине 1-Wire.
  • myWire.read()
    
    Считывает байт.
  • myWire.crc8(dataArray, length)
    
    Рассчитывает проверочный CRC для массива данных.

Фрагменты кода

#include <OneWire.h>

// температурный i/o чип DS18S20: 
OneWire ds(10);  // на 10-ом контакте

void setup(void) {
  // инициализируем входы/выходы;
  // запускаем последовательный порт:
  Serial.begin(9600);
}

void loop(void) {
  byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];

  ds.reset_search();
  if ( !ds.search(addr)) {
      Serial.print("No more addresses.\n");  //  "Адресов больше нет.\n")
      ds.reset_search();
      return;
  }

  Serial.print("R=");
  for( i = 0; i < 8; i++) {
    Serial.print(addr[i], HEX);
    Serial.print(" ");
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {
      Serial.print("CRC is not valid!\n");  //  "CRC не корректен!\n")
      return;
  }

  if ( addr[0] == 0x10) {
      Serial.print("Device is a DS18S20 family device.\n");  //  "Устройство принадлежит семейству DS18S20.\n")
  }
  else if ( addr[0] == 0x28) {
      Serial.print("Device is a DS18B20 family device.\n");  //  "Устройство принадлежит семейству DS18B20.\n")
  }
  else {
      Serial.print("Device family is not recognized: 0x");  //  "Семейство устройства не распознано.\n")
      Serial.println(addr[0],HEX);
      return;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);         // запускаем конверсию и включаем паразитное питание

  delay(1000);     // 750 миллисекунд может хватить, а может и нет
  // здесь можно использовать ds.depower(), но об этом позаботится сброс

  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // считываем scratchpad-память

  Serial.print("P=");
  Serial.print(present,HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // нам нужно 9 байтов
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print( OneWire::crc8( data, 8), HEX);
  Serial.println();
}

Более компактную версию этого кода, а также описание командного интерфейса смотрите тут.

Преобразование HEX-кода во что-то осмысленное (температуру)

Чтобы преобразовать HEX-код в температурные данные, сначала нужно определить, датчик какой серии вы используете – DS18S20 или DS18B20. Код, считывающий температуру с DS18B20DS1822), будет слегка отличаться, поскольку возвращает 12-битное температурное значение (точность 0,0626 градуса), а DS18S20DS1820) – 9-битное (точность 0,5 градуса).

Во-первых, вам нужно задать несколько переменных и пометить их прямо под loop() из примера выше.

int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

Для датчиков серии DS18B20 ниже Serial.println() нужно поместить следующий код:

LowByte = data[0];
  HighByte = data[1];
  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // проверяем значение в самом старшем бите
  if (SignBit) // если значение отрицательное
  {
    TReading = (TReading ^ 0xffff) + 1; 
  }
  Tc_100 = (6 * TReading) + TReading / 4;    // умножаем на (100 * 0.0625) или 6.25

  Whole = Tc_100 / 100;  // отделяем друг от друга целую и дробную порции 
  Fract = Tc_100 % 100;


  if (SignBit) // если число отрицательное
  {
     Serial.print("-");
  }
  Serial.print(Whole); 
  Serial.print(".");
  if (Fract < 10)
  {
     Serial.print("0");
  }
  Serial.print(Fract); 

  Serial.print("\n");

Этот фрагмент кода конвертирует температуру в градусы Цельсия и выводит ее через монитор порта.

Фрагмент кода для DS1820 с разрешением 0,5 градуса

Пример выше работает только с B-типом DS1820. Пример ниже демонстрирует, как работать с более низким разрешением DS1820 и множеством других сенсоров, выводящих значения на LCD.

Пример работает с 9-ым контактом Arduino, но для удобства можно воспользоваться каким-то другим. Учтите, впрочем, что 1-ый и 3-ий контакты DS1820 должны быть подключены к «земле». В этом примере резистор на 5 кОм подключен ко 2-ому контакту DS1820 и Vcc (+5V). О том, как подключить LCD к Arduino, читайте документацию к библиотеке LiquidCrystal.

#include <OneWire.h>
#include <LiquidCrystal.h>
// LCD=======================================================
// инициализируем библиотеку при помощи номеров контактов, формирующих собой интерфейс:
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
#define LCD_WIDTH 20
#define LCD_HEIGHT 2

/* температурный i/o интерфейс DS18S20 */

OneWire  ds(9);  // на 9-ом контакте
#define MAX_DS1820_SENSORS 2
byte addr[MAX_DS1820_SENSORS][8];
void setup(void) 
{
  lcd.begin(LCD_WIDTH, LCD_HEIGHT,1);
  lcd.setCursor(0,0);
  lcd.print("DS1820 Test");  //  "Тест DS1820"
  if (!ds.search(addr[0])) 
  {
    lcd.setCursor(0,0);
    lcd.print("No more addresses.");  //  "Адресов больше нет."
    ds.reset_search();
    delay(250);
    return;
  }
  if ( !ds.search(addr[1])) 
  {
    lcd.setCursor(0,0);
    lcd.print("No more addresses.");  //  "Адресов больше нет."
    ds.reset_search();
    delay(250);
    return;
  }
}
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
char buf[20];

void loop(void) 
{
  byte i, sensor;
  byte present = 0;
  byte data[12];

  for (sensor=0;sensor<MAX_DS1820_SENSORS;sensor++)
  {
    if ( OneWire::crc8( addr[sensor], 7) != addr[sensor][7]) 
    {
      lcd.setCursor(0,0);
      lcd.print("CRC is not valid");  //  "CRC не корректен"
      return;
    }

    if ( addr[sensor][0] != 0x10) 
    {
      lcd.setCursor(0,0);
      lcd.print("Device is not a DS18S20 family device.");  //  "Устройство не принадлежит семейству DS18S20."
      return;
    }

    ds.reset();
    ds.select(addr[sensor]);
    ds.write(0x44,1);         // запускаем конверсию и включаем паразитное питание

    delay(1000);     // 750 миллисекунд может хватить, а может и нет
                     // здесь можно использовать ds.depower(), 
                     // но об этом позаботится сброс 

    present = ds.reset();
    ds.select(addr[sensor]);    
    ds.write(0xBE);         // считываем scratchpad-память

    for ( i = 0; i < 9; i++) 
    {           // нам нужно 9 байтов
      data[i] = ds.read();
    }

    LowByte = data[0];
    HighByte = data[1];
    TReading = (HighByte << 8) + LowByte;
    SignBit = TReading & 0x8000;  // проверяем значение в самом старшем бите
    if (SignBit) // если число отрицательное
    {
      TReading = (TReading ^ 0xffff) + 1;
    }
    Tc_100 = (TReading*100/2);    

    Whole = Tc_100 / 100;  // отделяем друг от друга целую и дробную порции
    Fract = Tc_100 % 100;

    sprintf(buf, "%d:%c%d.%d\337C     ",sensor,SignBit ? '-' : '+', Whole, Fract < 10 ? 0 : Fract);

    lcd.setCursor(0,sensor%LCD_HEIGHT);
    lcd.print(buf);
  }
}

Дополнительные библиотеки для устройств типа 1-Wire

Примеры

  • DS18x20 Temperature - Этот пример демонстрирует, как при помощи библиотеки OneWire считывать данные с температурных датчиков типа 1-Wire. Поддерживаются модели DS18S20, DS18B20 и DS1822.
  • DS2408 Switch - Этот пример показывает, как при помощи библиотеки OneWire работать с PIO-чипом DS2048.
  • DS250x PROM - Этот пример показывает, как считывать данные с PROM-чипа семейства DS250x.

См.также

Внешние ссылки