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

Просмотров: 61616Комментарии: 31
Электроника. СхемотехникаAVR.Начинающим

Шина 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() и выводим данные на экран.

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


Комментариев: 31 RSS

1 Dmitry 19-02-2011 03:54

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

2 Albert 13-05-2011 19:36

(int)temp *= 0.0625 - здесь, по видимому, ошибка! Переменная temp должна быть float.

3 Dmitry 13-05-2011 21:18

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

4 ohmjke 13-08-2011 02:11

Спасибо огромное, статья очень помогла, ничего подобного в сети не нашел!

5 comp 02-08-2012 15:07

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

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

6 Николай 08-08-2012 15:51

у меня работает именно с 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. Потом её сдвинуть как было показано выше для отбрасывания дробной части и готова температура в градусах!

7 Dmitry 14-08-2012 16:18

Спасибо, за полезный комментарий) Будем совершенствоваться))

8 vi 28-10-2012 16:13

В w1_receive_byte переменная data не инициализирована!

9 aivs 05-04-2013 21:35

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

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

заменил на:

temp /= 16;

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

10 Юлий 30-04-2013 01:55

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

11 asder 23-06-2013 03:31

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

12 Антон Федоровский 20-12-2013 16:45

Переменная temp должна быть float.

13 Евгений 24-02-2015 06:01

*((char *)&Temp;)

не могли бы обьяснить это выражение? Не могу понять, что здесь происходит.

14 Артем 24-10-2015 18:21

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

15 Дмитрий 21-11-2015 12:58

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

16 michael 05-12-2015 04:57

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

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

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

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

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

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

17 michael 05-12-2015 06:23

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

18 michael 07-12-2015 01:50

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

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

19 Dmitry 07-12-2015 02:30

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

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

20 michael 07-12-2015 04:46

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

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

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

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

21 Dmitry 07-12-2015 22:55

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

22 michael 08-12-2015 01:08

непойму почему так:

if(temp > 1000)//если температура

23 michael 08-12-2015 01:10

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

24 Dmitry 08-12-2015 04:59

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

25 michael 09-12-2015 02:14

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

26 michael 09-12-2015 06:28

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

27 michael 09-12-2015 06:30

на atmega8 код же работает на частоте 8 Мгц

28 Владимир 20-12-2015 16:05

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

В таком случае битовые операции работать не будут.

29 Konst 04-02-2016 21:25

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

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

dkm1949@ya.ru

Спасибо.

30 Алексей 19-04-2016 03:12

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

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

grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question

Используйте нормальные имена. Ваш комментарий будет опубликован после проверки.

Вы можете войти под своим логином или зарегистрироваться на сайте.

(обязательно)