PIC мк. Эксперимент №16. One-wire на примере DS18b20.

Задача: Измерение температуры с помощью датчика DS18b20

Исходный материал: PIC16f628a, DS18b20, MAX232 level converter, devboard, proteus.

В данной статье рассмотрен пример работы в случае присутствия на линии одного датчика DS18b20, также здесь приводится текст стандартных функций для работы с протоколом 1-wire.

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

sch

Линия данных должна быть обязательна подтянута к питанию через резистор (в даташите рекомендуют 4.7 кОм). Курим даташит дальше – смотрим на организацию памяти:

Всего 9 байт памяти:

  1. младший байт значения температуры
  2. старший байт значения температуры
  3. триггер срабатывания по максимуму
  4. триггер срабатывания по минимуму
  5. конфигурационный регистр
  6. внутренние нужды
  7. внутренние нужды
  8. внутренние нужды
  9. CRC

Из это кучи нас интересуют 0, 1 и 4 байт (и то 4 постольку поскольку).

4 байт – конфигурационный регистр:

В нем мы можем перезаписывать только 6 и 5 биты отвечающие за разрешение измерений. По дефолту датчик измеряет с 12 битным разрешением, что соответствует точности 0.0625 градуса. Возможны также следующие варианты:

  • 9 бит – 0.5 градуса
  • 10 бит – 0.25 градуса
  • 11 бит – 0.125 градуса

Ниже представлена таблица значений битов и значений получаемого разрешения:

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

Посмотрим, что собой представляет непосредственно значение температуры в первых двух байтах:

В младшем байте 4 младших бита отвечают за дробную часть, целое значение берется из старших бит младшего и трех младших бит старшего байта. Биты S отвечают за знак значения (Обратите внимание, что для конвертации отрицательных значений температуры, нужно применять другие правила конвертации двоичного кода в десятичный) – если биты S = 1 — температура отрицательна, ну и соответственно наоборот.

Рассмотрим базовые процедуры для работы с one-wire, любая система команд по протоколу начинается с инициализации:

Для успешной процедуры, микроконтроллер должен провалить линию минимум на 480 мкс, затем отпустить и посмотреть провалит ли ее вслед за ним наш датчик, если провал есть – все нормально датчик присутствует. Напишем под это дело свою функцию, не забывая что в данном случае мы оперируем лишь состояниями порта (вход/выход) а  не значениями.

предварительно объявим такое дело:

#define STATE TRISB4
#define PIN RB4

 

static bit INIT(void){
static bit b;
STATE = 1;
STATE = 0;         //Проваливаем линию
__delay_us(500);   //Ждем 500 мкс
STATE = 1;         //Переключаемся на вход
__delay_us(65);    //Ждем 65 мкс
b = PIN;           //Смотрим чего там на линии
__delay_us(450);   //Дожидаемся до положенного временного интервала
return b;          //Возвращаем 0 или 1
}

Функция возвращает 0 если устройство присутствует на шине и 1 если устройства не обнаружено. Дальше нам надо сварганить функцию для записи в микросхему:

Чтобы передать устройству 0, нужно опустить линию минимум на 60 мкс, для передачи 0 – проваливаем шину минимум на 1 мкс, а затем отпускаем и выдерживаем нужное время. Учитываем то, что биты должны передаваться младшим вперед:

void TX(unsigned char cmd){

unsigned char temp = 0;
unsigned char i = 0;
temp = cmd;
for (i=0;i<8;i++) {
                  if (temp&0x01) {
                                 STATE = 0;              //передаем 1
                                 __delay_us(5);
                                 STATE = 1;
                                 __delay_us(70);
                                 } else {                //передаем 0
                                        STATE = 0;
                                        __delay_us(70);
                                        STATE = 1;
                                        __delay_us(5);
                                        }
                                 temp >>= 1;
                 }
}

Теперь по чтению:

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

UPDATE: Предыдущее высказывание неверно, и виной этому моя невнимательность, нам нужно не выждать 15 мкс, а успеть за эти 15 мкс снять значение от датчика.

Поэтому я предлагаю из старой функции убрать 10 микросекундную задержку:

unsigned char RX() {

unsigned char d = 0;
unsigned char i = 0;
for (i=0;i<8;i++){
                 STATE = 0;                 //прижимаем линию
                 __delay_us(6);
                 STATE = 1;
                 //__delay_us(10);            //дожидаемся больше чем 15 мкс --убираем нафиг, т.к. это идет в разрез с даташитом
                 d>>=1;                     //освобождаем место под новый бит
                 if (PIN == 1) d |= 0x80;   //если 1 то записываем 1
                 __delay_us(70);            //ждем до положенного времени
                 }
return d;
}

Этих трех функций достаточно для работы с протоколом 1-wire. Для того чтобы снять показания температуры нам нужно выполнить ряд команд (для случая одного датчика на шине):

  1. инициализация
  2. 0xCC – пропускаем идентификацию
  3. 0х44 – запуск конвертации температуры
  4. ждем необходимое время(750 мс для 12 битного разрешения)
  5. повторная инициализация
  6. 0xCC – пропускаем идентификацию
  7. 0хBE – читаем регистры

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

void get_temp() {
static bit init;

unsigned char temp1;
unsigned char temp2;
init = INIT();
             if (!init) {                 //успешно инициализировались?
                        TX(0xCC);
                        TX(0x44);
                        __delay_ms(150);  //ждем 750 мс
                        __delay_ms(150);
                        __delay_ms(150);
                        __delay_ms(150);
                        __delay_ms(150); }
              init = INIT();              //повторная инициализация
              if (!init) {
                         TX(0xCC);
                         TX(0xBE);        //команда на чтение
                         temp1 = RX();    //читаем младший байт
                         temp2 = RX();    //читаем старший байт
                          }

temp_drob = temp1 & 0b00001111;           //Записываем дробную часть в отдельную переменную
temp_drob = ((temp_drob*6)+2)/10;         //Переводим в нужное дробное число
temp1 >>= 4;
sign = temp2 & 0x80;                      //определяем знак температуры
temp2 <<= 4;
temp2 &= 0b01110000;
temp2 |= temp1;                           //помещаем все в одну переменную

if (sign) {                               //если минус
            temperature = 127-temp2;      //глобальная переменная
            temp_drob = 10 - temp_drob;   //глобальная переменная
           }   else temperature = temp2;
}

Чтобы проверить работоспособность я собрал схему на макетке и вывел информацию через уарт:

void main() {
unsigned char input = 0;

init_comms();
get_temp();
printf("\ftemperatura -- ");
if (sign) printf("-"); else printf("+");
printf("%d", temperature);
printf(".%d", temp_drob);

while(1){
        input = getch();
        if (input == 50) {
                         get_temp();
                         printf("\r\ntemperatura -- ");
                         if (sign) printf("-"); else printf("+");
                         printf("%d", temperature);
                         printf(".%d", temp_drob);
                         }
       }
}

Вот что получилось:

Исходники обновленного проекта лежат на репе. Что и как обновилось если кому интересно может посмотреть на комменты мая 2014 года 🙂

PIC мк. Эксперимент №16. One-wire на примере DS18b20.: 114 комментариев

  1. Это стандартный макрос компилятора Hi-Tech. Организует задержку на заданное количество микросекунд. По аналогии для милисекунд существует макрос __delay_ms(). Более подробно можно о этих макросах почитать в мануале компилятора.

    PS. Для использования этих макросов в коде должно присутствовать определение частоты кварца
    #define _XTAL_FREQ 4000000 //здесь указывается нужная частота

    Ответить

  2. Хочу собрать термометр,прочитал статью начал собирать все до кучи.В функции void TX при компиляции пишет что переменная cmd не используется.Что тут делать?
    Warning [1090] D:\Electronica\Project\termometr\termometr.c; 111. variable “_cmd” is not used

    Ответить

  3. В вашем коде есть несколько неоднозначных моментов:
    1. Строки:

    float temperature = 0;
    float temp_drob = 0;
    

    не имеют особого смысла, т.к. функции возвращают целочисленные значения + у вас нет глобальной переменной sign, которая отвечает за знак температуры, предлагаю такой вариант переменных:

    unsigned char temperature = 25  //инициализация с нормального значения температуры
    unsigned char temp_drob = 0;
    bit sign = 0;
    unsig
    

    2.

    void main ()
    {
    INIT();
    TX(cmd);
    RX();
    get_temp()
    }
    

    В частности строка TX(cmd); – я не понимаю как ее пропустил компилятор, такая переменная нигде в коде не объявлялась.
    3. Чтобы проверить работоспособность достаточно вызвать функцию get_temp(), внутри нее используются все остальные, а потом в уарт выпихнуть значение переменной temperature и temp_drob

    Ответить

  4. Доброго времени суток!
    собрал схему согласно Вашей статьи, программку написал, но датчик не отвечает и не распознается, т.е.
    …….
    if (INIT())
    то послать на через USART 1
    else
    послать ноль…

    по этому поводу возникло два вопроса: 1 не мог ли датчик сгореть (включал его точно так как в даташите и у Вас в статье)
    2 несоответствие временной задержки.

    насчет второго уже голову сломал, т.к. по теории в расчете на 1мц получается приходится времени: (4 такта / 4000000 Гц) = 1 мкс или как в моём случае 4/20000000=0.2мкс
    а! в программе Proteus получается, что совершенно по другому.
    если не трудно то можете привести другой пример временных задержек?

    Ответить

  5. Нашел решение!!!!
    в программе выше изложенной, а точнее

    static bit INIT(void){
    static bit b;
    STATE = 1;
    STATE = 0; //Проваливаем линию

    тут вместо STATE 1-го необходимо заменить на PIN = 0;

    Ответить

  6. Доброе утро, Вы нашли неверное решение: для успешной процедуры инициализации необходимо, чтобы датчик сам провалил линию в 0, а в вашем случае этого произойти не может, так как вы железно подтянули линию микроконтроллером к земле. Дальше программа естественно тоже работать не будет. Если не работает мой код, то:
    1. Надо проверить схему подключения.
    2. Надо попробовать другой датчик (может этому уже конец пришел).
    3. Попробовать поиграться с задержками.

    Вообще, чтобы не гадать, лучше всего посмотреть вашу схемку и код.

    Ответить

  7. sarge отправил на Ваш почтовый ящик текст программы.
    заранее скажу что я не использовал задержку, которую Вы предложили – __delay_us() и __delay_ms() т.к. при компиляции показывает ошибку

    Error[482] : symbol “_DelayMs” multiply defined in “C:\HTSOFT\project\DS18b20(2)\DS18b20.obj”
    Error[482] : symbol “_DelayMs” multiply defined in “C:\HTSOFT\project\DS18b20(2)\DS18b20.obj”
    Error[499] : undefined symbol:
    ___delay_us (C:\HTSOFT\project\DS18b20(2)\DS18b20.obj)

    Ответить

  8. Ошибку показывает потому, что у вас нет макроса:

    #define _XTAL_FREQ 4000000 //ну или другая ваша частота

    Как вы в своем коде проверяли что формируется нужная задержка?

    Ответить

  9. через счетчик MPLAB StopWatch

    строчку “#define _XTAL_FREQ 4000000 //ну или другая ваша частота”
    пробовал с ней, тоже самая ошибка :(((((
    а можете пример написать работы с этим таймером, с шапкой в том числе?

    Ответить

  10. )))) хм думаю Вы правы
    тут еще одну встроенную задержку нашел, она находится в директории
    C:\Program Files\HI-TECH Software\PICC\9.50\samples\delay
    можете что ни будь про неё рассказать?

    Ответить

  11. Ну это не встроенная задержка, это просто образец функции в папке с примерами. Вообще неважно каким образом будет делаться задержка, можете и на встроенных таймерах все это сделать, просто у меня сделано макросами, и чтобы что-то сказать мне надо заново все проделать, а мне этого совсем не хочется.
    Да и таймеры у меня обычно заняты более важными делами, тут и макросы отлично справляются.

    Ответить

  12. Работает!)))
    дело в том, что у меня частота кварца 20МГц, а не 4, и по этому получалось, что должным образом задержка не обеспечивалась.
    Не знаю по каким причинам но __delay_us() и __delay_ms() не распознавал компилятор, по этому воспользовался
    C:\Program Files\HI-TECH Software\PICC\9.50\samples\delay
    а временную задержку уже подгонкой подобрал..
    меня еще одна строчка заинтересовала:
    printf(“%d”, temperature); –это тоже является макросом MPLAB, я так понимаю.
    Можете рассказать что нибудь про него по детальней, а то в интернете ссылаются на команды С++.

    Ответить

  13. “printf(«%d», temperature); —это тоже является макросом MPLAB, я так понимаю.” ой! ошибся HI – TECH

    Ответить

  14. не, это стандартная функция С библиотеки stdio.h, в данном случае она выводит строку через уарт, но в целом ничем не отличается от обычной printf, так что про нее читать можно в любом источнике

    Ответить

  15. здравствуйте уважаемый sarge. Помогите пожалуйста написать прошивку для устройства которое будет работать следующим образом. DS18B20 будет установлен на радиаторе, при температуре радиатора выше 40 гр на одном из портов ввода-вывода лог1 , а при понижении температуры до 30 лог0. Спасибо!

    Ответить

    sarge Ответил:

    Добрый день,
    Ваша задача выглядит довольно просто:
    1. Заводим таймер с нужным интервалом проверки
    2. При прерывании по таймеру берем значение температуры функцией (get_temp())
    3. Если температура больше или равна 40 и статус = 0, то на выход 1
    иначе, если температура меньше или равна 30 и статус = 1, то на выход 0.

    Вот и все, функции для работы с датчиком можно взять прямо в этой статье, про таймеры на сайте также есть разделы.

    Ответить

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

    Ответить

    sarge Ответил:

    Ну показывайте код. Так я не могу сказать почему он ругается 🙂

    Ответить

  17. Добрый вечер! Стоит передо мной задача как у melekhov’a. Пока пытаюсь смоделировать в протеусе, камень 12F629. Все функции кроме main() слизаны отсюда, get_temp() крутится в основном цикле. Но температура всегда 0, в чем проблема?
    [spoil]
    int temperature;//целая часть
    int temperature_fraction;//дробная часть
    bit init;
    bit sign;//знак
    void init_uc();
    void get_temp(void);

    void main(){
    init_uc();
    while(1){
    get_temp();
    if(temperature > 50) start_funs = 1;
    if(temperature < 40) start_funs = 0;
    }
    }

    void init_uc(){
    temperature = 25;
    temperature_fraction = 0;
    CMCON = 0xFF;
    GPIO = 0;
    TRIS0 = 0;
    TRIS1 = 0;
    OPTION=0b00001000; //Устанавливаем предделитель TMR0 1:1
    WPU=0b00010100;
    IOCB=0b00000000;
    }[/spoil]

    Ответить

    sarge Ответил:

    На линии 1-wire в протеусе изменения какие-либо происходят если осциллом посмотреть? Или еще лучше сразу снимите график и оцените все ли правильно происходит.

    Ответить

    150BillionSteps Ответил:

    Посмотрел осциллографом. Первый цикл проходит как надо, но с датчика приходит не та температура (74 – установлено, а приходит 58). На последующих циклах осциллограф рисует все время прямую. Температура приходит так 47, 127, 0, 0..0…Я отправил Вам на почту свой проект, буду очень благодарен, если посмотрите. На макетке не заработало.

    Ответить

    sarge Ответил:

    1. В архиве нет файла для протеуса.
    2. Обычно макрос __delay_ms() работает до определенной максимальной цифры (255 вроде).
    3. Зачем опрашивать датчик каждую секунду?

    Ответить

    150BillionSteps Ответил:

    1. Файл-проект для протеуса *.pdsprj? Я отправил его повторно, но он был в архиве.
    2. __delay_ms(2000) работал у меня в том числе и на железе. Поменял на пять __delay_ms(200), результат тот же.
    3. Но ведь это не принципиально, все необходимые задержки вроде есть в get_temp(). МК питаться будет от сети.
    Кстати, закомментировал вот эту часть кода
    if (sign) { //если минус
    temperature = 127-temp2; //глобальная переменная
    temperature_fraction = 10 – temperature_fraction; //глобальная переменная
    }
    и температура стала приходить другая, это удивительно, ведь я всегда устанавливал положительную температуру и эти инструкции не должны выполняться. Старший бит от DS18B20 0b00000XXX.

    Ответить

    sarge Ответил:

    Мой протеус 7 не открывает pdsprj файлы.
    У меня тут сейчас со временем большой напряг, поэтому я хотел бы, чтобы вы перекинули свой код на 628 камень и проверили по уарту например, адекватно ли температура отображается.
    Если надо могу прислать свой проект с файлами протеуса под 628а.

    Ответить

    150BillionSteps Ответил:

    Да, оправьте, пожалуйста, на giper-utesov@yandex.ru.

    Ответить

  18. Здравствуйте sarge. Очень нравится ваш сайт, в статьях всё очень подробно описано!
    Разбираю ваш код, столкнулся с проблемой.
    в Datasheet DS18B20 описано :DS18B20 будет формировать (ответ на слот времени чтения от
    устройства управления) логический «0» когда происходит температурное преобразование. И
    логическую «1», когда конвертирование выполнено.

    у вас в коде:

    TX(0xCC);
    TX(0x44);
    __delay_ms(150); //ждем 750 мс
    __delay_ms(150);
    __delay_ms(150);
    __delay_ms(150);
    __delay_ms(150); }
    init = INIT(); //повторная инициализация
    if (!init) {
    TX(0xCC);
    TX(0xBE); //команда на чтение
    temp1 = RX(); //читаем младший байт
    temp2 = RX(); //читаем старший байт
    }
    Объясните пожалуйста повторной инициализацией вы проверяете выполнено конвертирование или нет?
    Если да то мне не понятно, ведь тогда получается что если “0” то
    TX(0xCC);
    TX(0xBE); //команда на чтение
    а в Datasheet всё наоборот.

    Ответить

    sarge Ответил:

    Фух, уже столько времени прошло, если я правильно вспомнил, то в даташите есть такая строчка

    All transactions on the 1-Wire bus begin with an initialization sequence. The initialization sequence
    consists of a reset pulse transmitted by the bus master followed by presence pulse(s) transmitted by the
    slave(s).

    Оттуда же:

    It is very important to follow this sequence every time the DS18B20 is accessed, as the DS18B20 will not
    respond if any steps in the sequence are missing or out of order

    Под транзакциями понимается последовательность из двух команд. Так что я вроде как просто следовал даташиту и проверял не завис ли девайс.
    Успешность конвертирования я не проверяю.

    Ответить

    Александр Ответил:

    Понятно,так как не проверяете успешность конвертирования поэтому у вас стоит задержка в 750ms как описано вDatasheet чтобы датчик успешно конвертировал.
    Просто меня смутило то, что когда я убрал задержку 750ms всё продолжало успешно работать.
    Спасибо! Буду дальше разбираться.

    Ответить

  19. Добрый день! Прошу рассказать или ткунть пальцем, где самому почитать. Про хитрое преобразование двоичного кода показаний с термодатчика в десятичное число. Несколько не понятны мне те семь строчек со здвигом и & (поразрядное и) кажется. Так если не напрягаться то просто бы слизал ваш код, но мне на будущее придется делать преобразование двоичного кода в шеснадцатеричный.

    Ответить

    sarge Ответил:

    Ээ, в даташите 🙂

    Ответить

  20. Добрый вечер . В описании функции приема байта есть коментарий , что после подсаживания шины ждем больше 15 микросекунд а затем смотрим что там у нас 1 или 0. Так вот по факту изучив даташит. Состояние шины нужно смотреть сразу, и не более чем через 15 мкс.
    Из- за этого коментария потратил лишний час 🙁

    Ответить

    sarge Ответил:

    Все правильно в комментарии, посмотрите на картинку внимательно, мы изначально проваливаем шину на некоторое время ( 6 мкс у меня ), переводим состояние пина на вход и ждем еще как минимум 10 мкс (в сумме 16 мкс) и проверяем что у нас там 0 или 1.
    То же и на картинке.
    Так в чем ваш вопрос или вам просто хотелось высказаться?

    Ответить

    Alexey Ответил:

    И это конечно тоже. Но пока не убрал задержку на экране были все нули.

    Ответить

    Alexey Ответил:

    На картинке master read 0 slot внизу показано время где нужно роверить состояние на входе Master samples. Там видно, что нужно проверить за время не более 15 мкс. А пишу для того, что наверняка найдетсся еще не один товарищ который будет повторять опыты с вашего сайта. Но не просто тупо копируя исходники, а читая коментарии и напрягая Мозг 🙂

    Ответить

    sarge Ответил:

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

    Ответить

  21. А как реализовать опрос двух трех датчиков по одной линии внятных примеров я ненашел, если не трудно может раскажете ?Спасибо.

    Ответить

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.