Samou4ka » 🐞 AVR.Начинающим » Интерфейс 1-Wire и температурный датчик DS18B20

Интерфейс 1-Wire и температурный датчик DS18B20

Шина 1-Wire привлекательна, в первую очередь, тем, что использует только одну линию связи. Разберем 1-Wire немного подробней. Для работы по этому интерфейсу должно быть одно ведущее устройство и одно или несколько ведомых. У каждого 1-Wire устройства есть 64-битный уникальный код. Используя который, ведущий определяет с каким из устройств на линии он будет работать. Но узнать этот код это отдельный разговор и в этой статье будем считать, что у нас всего одно устройство подключено к шине.

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

Итак, чтобы работать с любым устройством нам нужно инициализировать его, послать ему команду и принять какие-то данные. Разберем как это можно сделать на шине 1-Wire:

  1. Чтобы произвести поиск устройства на линии, ведущее устройство (в нашем случае микроконтроллер) должно удерживать на сигнальной линии логический «0» по крайней мере 480мкс (так называемый Reset Pulse), потом отпустить шину минимум на 60мкс и после этого посмотреть уровень на линии. Если на линии высокий уровень — устройств 1-Wire не обнаружено, если низкий — есть хотя бы одно устройство (Presence Pulse).
  2. Теперь мы должны послать команду устройству. Передача информации разбита на тайм-слоты. Один тайм-слот служит для передачи одного бита команды и длительность его находится в диапазоне 60-120мкс. Данные передаются, начиная с младшего бита. Для передачи логического нуля МК удерживает на линии «0» минимум 60мкс, а потом отпускает шину минимум на 1мкс. Для передачи логической единицы МК удерживает на линии «0» минимум 1мкс, но не больше 15мкс, а затем отпустить шину на оставшееся время тайм-слота.
  3. Прием данных с устройства 1-Wire. Чтобы принять данные МК удерживает на линии «0» не менее 1мкс, а затем отпускает шину, немного ждет и смотрит состояние линии(обычно до 15-й микросекунды от начала тайм-слота). Если на линии «1» — значит передается «1», если «0» — значит «0».

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

Принцип работы шины мы рассмотрели. Давайте теперь померяем температуру. Для измерения будем использовать датчик DS18B20. Начнем с того, что откроем даташит и посмотрим основные моменты, которые нам понадобятся:

  1. Датчик может обеспечивать 9-, 10-, 11-, 12-ти битное разрешение измерения температуры в градусах Цельсия. Это разрешение мы можем выбирать, записывая соответствующие данные в конфигурационный регистр датчика (по умолчанию 12-битное разрешение).
  2. Измеряет в диапазоне -55…+125оС. С точностью 0.5 градуса.
  3. На единицу кода преобразованного значения температуры приходится 0.0625оС.
  4. При чтении данных с датчика мы принимаем 9 байт данных, из которых мы будем брать 0-й и 1-й, младший и старший байт температуры соответственно. В старшем байте в битах 11-15 содержится информация о знаке температуры (если в этих битах единица, тогда температура ниже нуля).
  5. Команды для работы с датчиком. Т.к. у нас будет одно устройство на линии, поэтому нам не нужен 64-битный код датчика, поэтому пропустим код и воспользуемся командой — Skip ROM (0xCC). Команда преобразования температуры — Convert T (0x44). Команда чтения данных — Read Scratchpad (0xBE).
  6. Время преобразования температуры с 12-битной точностью занимает max 750мс. Т.е. это время нужно будет подождать после вызова команды преобразования температуры (0х44) и только потом посылать следующую команду.
  7. После преобразования температуры, прежде чем посылать следующую команду нужно еще раз послать импульс сброса (Reset pulse) и получить ответный импульс (Presence Pulse).

Вроде бы все, что нам может понадобиться в этой статье. Подробно рассматривать все что можно сделать с этим датчиком мы не будем, т.к. наша главная цель разобраться с шиной 1-Wire. Теперь подключим датчик к МК и напишем программу для работы с ним.

Воспользуемся следующей схемой.

Обвязка МК и ЖКИ по питанию не показана.

Здесь мы подключили датчик к нулевому биту порта С, на самом деле в функциях программы старался не привязываться к конкретной ножке, а делал так, чтобы в начале программы можно было определять куда присоединен датчик. Вообще функции программы, которые связаны с шиной 1-Wire (начинаются с w1_…) можно использовать с любым 1-Wire устройством, а не только с температурным датчиком описанным здесь. Итак, считаем что со схемой разобрались и перейдем к фрагментам программы. Сначала рассмотрим функции для работы с шиной 1-Wire.

//определяем порт и бит к которому подключено устройство 1-wire
#define W1_PORT PORTC
#define W1_DDR DDRC
#define W1_PIN PINC
#define W1_BIT 0
//функция определяет есть ли устройство на шине
unsigned char w1_find()
{
unsigned char device;
W1_DDR |= 1<<W1_BIT;//логический «0»
_delay_us(485);//ждем минимум 480мкс
W1_DDR &= ~(1<<W1_BIT);//отпускаем шину
_delay_us(65);//ждем минимум 60мкс и смотрим что на шине
if((W1_PIN & (1<<W1_BIT)) ==0x00)
device = 1;
else
device = 0;
_delay_us(420);//ждем оставшееся время до 480мкс
return device;
}
//функция посылает команду на устройство 1-wire
void w1_sendcmd(unsigned char cmd)
{
for(unsigned char i = 0; i < 8; i++)//в цикле посылаем побитно
{
if((cmd & (1<<i)) == 1<<i)//если бит=1 посылаем 1
{
W1_DDR |= 1<<W1_BIT;
_delay_us(2);
W1_DDR &= ~(1<<W1_BIT);
_delay_us(65);
}
else//иначе посылаем 0
{
W1_DDR |= 1<<W1_BIT;
_delay_us(65);
W1_DDR &= ~(1<<W1_BIT);
_delay_us(5);
}
}
}
//функция читает один байт с устройства 1-wire
unsigned char w1_receive_byte()
{
unsigned char data=0;
for(unsigned char i = 0; i < 8; i++)//в цикле смотрим что на шине и сохраняем значение
{
W1_DDR |= 1<<W1_BIT;
_delay_us(2);
W1_DDR &= ~(1<<W1_BIT) ;
_delay_us(7);
if((W1_PIN & (1<<W1_BIT)) == 0x00)
data &= ~(1<<i);
else
data |= 1<<i;
_delay_us(50);//задержка до окончания тайм-слота
}
return data;
}

Теперь рассмотрим функцию, которая читает первые два байта с датчика DS18B20 и возвращает значение температуры.

//функция преобразует полученные с датчика 18b20 данные в температуру
int temp_18b20()
{
unsigned char data[2];
int temp = 0;
if(w1_find()==1)//если есть устройство на шине
{
w1_sendcmd(0xcc);//пропустить ROM код, мы знаем, что у нас одно устройство или передаем всем
w1_sendcmd(0x44);//преобразовать температуру
_delay_ms(750);//преобразование в 12 битном режиме занимает 750ms
w1_find();//снова посылаем Presence и Reset
w1_sendcmd(0xcc);
w1_sendcmd(0xbe);//передать байты ведущему(у 18b20 в первых двух содержится температура)
data[0] = w1_receive_byte();//читаем два байта с температурой
data[1] = w1_receive_byte();
//загоняем в двух байтную переменную
temp = data[1];
temp = temp<<8;
temp |= data[0];
//переводим в градусы
temp *= 0.0625;//0.0625 градуса на единицу данных
}
//возвращаем температуру
return temp;
}

Чтобы работать с функцией нужно в цикле функции main() поступить следующим образом

temp = temp_18b20();
if(temp > 1000)//если температура <0
{
temp = 4096 — temp;
temp = -temp;
}

Дальше отправляем temp в какой нибудь буфер используя функцию sprintf() и выводим данные на экран.

Посмотрите комментарии, там уже появилось много полезной информации.

Datasheet DS18B20

Как вам статья? Ваша реакция:
+1
0
+1
0
+1
0
+1
0
+1
0
+1
0
Расскажите друзьям:
Оцените статью:

32 комментария

  • желательно в код добавить строку с определением частоты работы МК #define F_CPU 4000000

  • у меня работает именно с int. сразу отбрасывается дробная часть и вывожу целое число

  • «Огромное спасибо!

    Как раз искал пример низкоуровневой работы с 1-wire, а то везде или готовые специализированные библиотеки или асм без комментариев.»

  • «у меня работает именно с int. сразу отбрасывается дробная часть и вывожу целое число
    Ну и программисты нынче пошли… спасибо поржал!
    temp *= 0.0625;//0.0625 градуса на единицу данных
    Что делается в этой строчке? Берем переменную типа int, приводим к типу float, умножаем на 0.0625, приводим обратно к типу int и записываем в переменную. И все это на 8-мибмитном контролере!
    Кстати, а почему 0.0625 знаете? Конечно же нет… объясняю:

    DS18B20 использует арифметику с фиксированной точной(в отличии компилятора Си, который будет использовать мантиссу и порядок). младшие 4 бита это дробная часть(отсчет с старшего бита):

    1 бит — 1/2 = 0.5

    2 бит — 1/4 = 0.25

    3 бит — 1/8 = 0.125

    4 бит — 1/16 = 0.0625

    Все что надо сделать что бы получить температуру в градусах — сдвинуть Temp на 4 бита вправо, отбросив дробную часть:

    Temp = Temp >> 4;

    Теперь про оптимизацию. Зачем массив из двух байт? Зачем сдвиги? Зачем операция И? Это растрата памяти(которой в контроллере не мегабайты и гигабайты, а «»с гулькин нос»»), это замедление работы программы. Нужно было всего лишь сделать так:

    //читаем два байта с температурой

    *((char *)&Temp;) = w1_receive_byte();

    *((char *)&Temp; + 1) = w1_receive_byte();

    и температура сразу бы попала в Temp. Потом её сдвинуть как было показано выше для отбрасывания дробной части и готова температура в градусах!»

  • «У меня в Attiny2313 не залилось с такой строкой:

    temp *= 0.0625;//0.0625 градуса на единицу данных

    заменил на:

    temp /= 16;

    и все работает»

  • Спасибо за проделанную работу, весь день искал код «коротко и ясно», наконец нашел)

  • Здравствуйте. А как вывести дробную часть? Вместо дробной части все время показывает ноль

  • «*((char *)&Temp;)
    не могли бы обьяснить это выражение? Не могу понять, что здесь происходит.»

  • Помогите с инициализацией датчика ds18b20 на atmega8, слоты настроил 485 и 65 us но датчик не сбрасывает шину в 0

  • Большое спасибо за статью! Подскажите в терминологии — что значит «отпустить шину»?

  • «ничего не работает

    Код скопировал в atmel studio

    правил порты, скомпилировал прошивку , спробовал в протеусе … ничо.

    каково содержимое переменной temp в функции main.

    и что обозначает если >1000 это что градусы))

    Поясните пожалуйста.»

  • с вашей прошивкой и с повторенной схемой в протеусе на дисплей выводятся нули при любом показании датчика

  • «Почему коментарии удаляются, я что то не так написал?

    я всего лишь спросил то ,что мне непонятно.»

  • «Комментарии не удаляются, они просто ждут модерацииsmile

    Сейчас уже точно не отвечу — давно это было. Но в свое время все работало. Если значение больше 1000, то это отрицательная температура и она соответсвующим образом преобразуется»

  • «Не пойму что в результате будет в переменной temp ? Я думал там будет температура в градусах , мне например не нужно выводить на экранчик, а нужно просто проверить условие … допостим :

    if(temp > 50) — подать сигнал на такую-то ножку

    else — установить в ноль… ну так для примера просто делал.

    Почему-то не получается , подскажите почему ,пожалуйста. В протеусе у меня ничего не работает, хотя я уверен что я все выставил правильно. Я даже пытался вывести temp на ножки определенного порта, ну чтоб увидеть что там творится … там нули.»

  • ну по моим воспоминаниям делалось как раз чтобы на выходе была температура. Т.е. после условия (если зашло в него) будет отрицательная температура в градусах, если не зашло то положительная

  • в частности не пойму как там получается нечто похожее на 1000 (ведь у дачтика температура до 128 градусов) , изивините мне мою непонятливость.

  • функция temp_18b20 возвращает температуру, если положительная, то ничего не делаем. В случае отрицательной, она будет отсчитываться от 4096 (12 битная температура) в меньшую сторону, поэтому вычитаем в условии. 1000 выбрана судя по всему чисто условно, т.к. температура положительная точно не дойдет до 1000 градусов (по крайней мере на этом датчикеsmile)

  • Спасибо за обьяснения , заработало в протеусе , правда только на меге 8 ,на тиньке 13 пока-что не получается симулировать.

  • Чисто экспериментально установил что на тиньке 13 с частотой больше 1 Мгц код не работает . Как с этим бороться не знаю … может у кого какие идеи.

  • «А как вывести дробную часть?
    Переменная temp должна быть float.
    В таком случае битовые операции работать не будут.»

  • «А кто-нибудь может помочь старику, не знающему С, поиметь Библиотеку для работы с 1Wire устройствами на ассемблере?

    Можно для AVR, можно для 51. Хорошо бы, если вместо привязки к таймерам написать просто время задержки.

    Спасибо.»

  • Подскажите пожалуйста, что нужно исправить для корректной работы DS1820 или DS18S20.

Оставить комментарий