Протокол 1-Wire

Изучите взаимодействие между устройствами или датчиками с использованием протокола OneWire.

Автор: Arduino Community
Последнее обновление: 15.11.2024

Примечание

Эта статья была обновлена 28.09.2022 Ханнесом Зибенайхером.

Важно

Термины «контроллер/периферийное устройство» ранее обозначались как «мастер/ведомый» (master/slave). Arduino больше не поддерживает использование этой терминологии. Устройства, ранее называвшиеся «master», теперь называются контроллером, а устройства, ранее называвшиеся «slave», — периферийными устройствами.

Протокол 1-Wire — это протокол, работающий через один провод между контроллером и периферийным устройством. В этой статье рассматриваются основы использования протокола 1-Wire с Arduino с помощью библиотеки OneWire. В следующих разделах содержится информация о протоколе 1-Wire, интерфейсах, питании, адресации устройств, чтении устройств и краткий экскурс в историю библиотеки.

Последняя версия

Последняя версия библиотеки находится на сайте Пола Штоффрегена.

В настоящее время библиотека OneWire поддерживается Полом Штоффрегеном. Если вы обнаружите ошибку или у вас есть предложение по улучшению библиотеки, напишите на paul@pjrc.com. Убедитесь, что вы используете последнюю версию OneWire.

Bus — это подкласс библиотеки OneWire. Класс Bus сканирует шину 1-Wire, подключённую к аналоговому выводу, и сохраняет ROM-коды в массиве. В классе Bus доступны несколько методов для получения данных от различных датчиков 1-Wire (DS18B20, DS2438).

Протокол 1-Wire

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

В сети 1-Wire, которую Dallas назвала «MicroLan» (торговая марка), одно контроллерное устройство взаимодействует с одним или несколькими периферийными устройствами 1-Wire по одной линии данных, которая также может использоваться для питания периферийных устройств. (Устройства, потребляющие питание от шины 1-Wire, работают в режиме паразитного питания.) Руководство по 1-Wire Тома Бойда может рассказать больше, чем вам хотелось бы знать, но оно также может ответить на вопросы и вдохновить интерес.

Температурные датчики 1-Wire стали особенно популярны, потому что они недорогие и простые в использовании — они предоставляют откалиброванные цифровые показания температуры напрямую. Они более устойчивы к длинным проводам между датчиком и Arduino. Пример кода ниже демонстрирует, как взаимодействовать с устройством 1-Wire, используя библиотеку Arduino OneWire Джима Штудта, на примере цифрового термометра DS18S20. Многие чипы 1-Wire могут работать как в паразитном, так и в обычном режиме питания.

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

Выделенный контроллер шины

Dallas/Maxim и ряд других компаний производят выделенные контроллеры шины для чтения/записи и управления сетями 1-Wire. Большинство из них перечислены здесь:

http://owfs.org/index.php?page=bus-masters

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

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

Ещё одним ключевым преимуществом является поддержка файловой системы чтения/записи с широкой поддержкой устройств для контроллеров 1-Wire, предоставляющей множество встроенных функций для широкого спектра типов устройств 1-Wire.

Контроллер UART/USART

Большинство UART/USART вполне способны поддерживать скорость, значительно превышающую 15,4 кбит/с, требуемую шиной 1-Wire в стандартном режиме. Что ещё важнее, тактирование и буферизация обрабатываются отдельно, снова разгружая основной процесс микроконтроллера или главного процессора. Данная реализация рассматривается здесь: http://www.maximintegrated.com/en/app-notes/index.mvp/id/214.

Подход «битбэнгинг»

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

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

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

Питание устройств OneWire

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

Режим паразитного питания

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

Этот ток обычно очень мал, но может достигать 1,5 мА при выполнении преобразования температуры или записи EEPROM. Когда периферийное устройство выполняет одну из этих операций, контроллер шины должен удерживать шину в подтянутом высоком состоянии для обеспечения питания до завершения операции; для преобразования температуры DS18S20 требуется задержка 750 мс. В течение этого времени контроллер не может выполнять никаких действий, например, отправлять команды другим устройствам или опрашивать состояние завершения операции периферийного устройства. Для поддержки этого библиотека OneWire позволяет удерживать шину в высоком состоянии после записи данных.

Нормальный режим (внешний источник питания)

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

Примечание

Замечание о резисторах:

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

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

Адресация устройства 1-Wire

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

Команды для одного устройства

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

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

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

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

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

Два основных этапа процесса чтения

Преобразование

Устройству отправляется команда для выполнения внутренней операции преобразования. Для DS18B20 это байт-команда Convert T (0x44). В библиотеке OneWire это выполняется как ds.write(0x44), где ds — экземпляр класса OneWire. После отправки этой команды устройство считывает внутренний АЦП, и когда этот процесс завершается, оно копирует данные в регистры Scratchpad. Длительность этого процесса преобразования варьируется в зависимости от разрешения и указана в техническом описании устройства. DS18B20 требует от 94 мс (9-битное разрешение) до 750 мс (12-битное разрешение) для преобразования температуры. Пока выполняется преобразование, устройство может опрашиваться, например, с помощью команды ds.read() в OneWire, чтобы выяснить, завершило ли оно преобразование успешно.

Чтение Scratchpad

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

Асинхронное и синхронное чтение/запись

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

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

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

Масштабируемость по скорости опроса при нескольких устройствах

Ещё одна серьёзная проблема метода «Преобразование, Ожидание, Чтение» заключается в том, что он плохо масштабируется, и без веской причины. Все команды преобразования можно отправить последовательно (или одновременно), отправив Skip ROM и затем команду преобразования, после чего результаты можно считать последовательно. Обсуждение этого вопроса доступно здесь: http://interfaceinnovations.org/onewireoptimization.html.

Корректировка времени ожидания под требуемое время преобразования

Наиболее эффективное и оперативное чтение устройств 1-Wire явно учитывает время преобразования читаемого устройства, которое, как правило, является функцией разрешения чтения. В примере ниже, например, задано 1000 мс, тогда как в техническом описании указано максимальное время преобразования 750 мс, а типичное преобразование занимает 625 мс или менее. Самое главное, значение должно быть скорректировано для текущего разрешения опроса. Преобразование с 9-битным разрешением, например, займёт 94 мс или менее, и ожидание 1000 мс просто не имеет смысла. Как было отмечено выше, наиболее эффективный способ опроса — использование слота чтения для опроса устройства. Таким образом можно точно знать, когда результат готов, и немедленно его получить.

История

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

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

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

Майлс Бёртон создал на её основе Dallas Temperature Control Library.

Пример кода

#include <OneWire.h>

// Ввод/вывод чипа температуры 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");
    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");
      return;
  }

  if ( addr[0] == 0x10) {
    Serial.print("Device is a DS18S20 family device.\n");
  }
  else if ( addr[0] == 0x28) {
    Serial.print("Device is a DS18B20 family device.\n");
  }
  else {
    Serial.print("Device family is not recognized: 0x");
    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. Код для чтения температуры должен несколько отличаться для DS18B20 (и DS1822), потому что он возвращает 12-битное значение температуры (точность 0,0625 градуса), тогда как DS18S20 и DS1820 возвращают 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");

Этот блок кода преобразует температуру в градусы Цельсия и выводит её в Serial Monitor.

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

Приведённый выше пример работает только для B-версии DS1820. Ниже представлен пример кода, который работает с DS1820 с меньшим разрешением и с несколькими датчиками, отображая их значения на LCD. Пример использует вывод 9 Arduino. При необходимости замените его на подходящий для вашей задачи вывод. Выводы 1 и 3 DS1820 должны быть подключены к земле! В примере резистор 5 кОм подключён от вывода 2 DS1820 к Vcc (+5 В). Для подключения 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

/* Ввод/вывод чипа температуры 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");

  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");
      return;
    }

    if ( addr[sensor][0] != 0x10)
    {
      lcd.setCursor(0,0);
      lcd.print("Device is not a DS18S20 family device.");
      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);
  }
}