Подключение датчика температуры DS18B20 к ATmega8 и вывод температуры на LCD HD44780

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating число найденных устройств) - Паразитное питаниеВ данной статье пойдет речь о том, как подключить датчик температуры DS18B20 к ATmega8 и отображать данные на ЖКИ-дисплее 16×1 на базе HD44780. Будут приведены три примеры программ работы с датчиком температуры, а именно: самый простой — подключение одного DS18B20 к ATmega8; подключение нескольких DS18B20 к ATmega8 на разные выводы микроконтроллера; самый сложный — подключение нескольких датчиков температуры DS18B20 к ATmega8 на одну шину. Для проверки работоспособности программ и схем был использован эмулятор Proteus 7 (ISIS 7 Professional). Код программ (проекты в Atmel Studio 7 целиком) вы сможете скачать по ссылке в конце статьи. После оптимизации кода вместо ATmega8 можно использовать более простой микроконтроллер ATtiny2313.

OneWire библиотека

config.h

#ifndef CONFIG_H_
#define CONFIG_H_


#define F_CPU              8000000UL
#define ONE_WIRE_PORT      PORTB
#define ONE_WIRE_DDR       DDRB
#define ONE_WIRE_PIN       PINB

#endif /* CONFIG_H_ */

 

OneWire.h

#ifndef ONEWIRE_H_
#define ONEWIRE_H_

#define CMD_CONVERTTEMP    0x44
#define CMD_RSCRATCHPAD    0xbe
#define CMD_WSCRATCHPAD    0x4e
#define CMD_CPYSCRATCHPAD  0x48
#define CMD_RECEEPROM      0xb8
#define CMD_RPWRSUPPLY     0xb4
#define CMD_SEARCHROM      0xf0
#define CMD_READROM        0x33
#define CMD_MATCHROM       0x55
#define CMD_SKIPROM        0xcc
#define CMD_ALARMSEARCH    0xec

void oneWireInit(uint8_t);
void writeBit(uint8_t);
void writeByte(uint8_t);
void setDevice(uint64_t);
void searchRom(uint64_t*, uint8_t&);
void skipRom(void);
uint8_t readByte(void);
uint8_t readBit(void);
uint8_t reset(void);
uint8_t crcCheck(uint64_t, uint8_t);
uint64_t readRoom(void);
uint64_t searchNextAddress(uint64_t, uint8_t&);

extern uint8_t ONE_WIRE_DQ;

#endif /* ONEWIRE_H_ */

OneWire.cpp

#define DEVICES_ERROR  1
#include "config.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "OneWire.h"

uint8_t ONE_WIRE_DQ = PINB0;

void oneWireInit(uint8_t pin) {
  ONE_WIRE_DQ = pin;
  ONE_WIRE_PORT |= (1 << ONE_WIRE_DQ);
  ONE_WIRE_DDR |= (1 << ONE_WIRE_DQ); // выход
}

/*
 * сброс
 */
uint8_t reset() {
  uint8_t response;

  // импульс сброса, минимум 480us
  ONE_WIRE_PORT &= ~(1 << ONE_WIRE_DQ);
  ONE_WIRE_DDR |= (1 << ONE_WIRE_DQ); // выход
  _delay_us(480);

  // Когда ONE WIRE устройство обнаруживает положительный перепад, он ждет от 15us до 60us
  ONE_WIRE_DDR &= ~(1 << ONE_WIRE_DQ); // вход
  _delay_us(60);

  // и затем передает импульс присутствия, перемещая шину в логический «0» на длительность от 60us до 240us.
  response = (ONE_WIRE_PIN & (1 << ONE_WIRE_DQ));
  _delay_us(410);

  // если 0, значит есть ответ от датчика, если 1 - нет
  return response;
}

/*
 * отправить один бит
 */
void writeBit(uint8_t bit) {
  if (bit & 1) {
    cli();
    // логический «0» на 1us
    ONE_WIRE_PORT &= ~(1 << ONE_WIRE_DQ);
    ONE_WIRE_DDR |= (1 << ONE_WIRE_DQ); // выход
    _delay_us(10);
    sei();
    ONE_WIRE_DDR &= ~(1 << ONE_WIRE_DQ); // вход
    _delay_us(55);
  } else {
    cli();
    // логический «0» на 1us
    ONE_WIRE_PORT &= ~(1 << ONE_WIRE_DQ);
    ONE_WIRE_DDR |= (1 << ONE_WIRE_DQ); // выход
    _delay_us(65);
    ONE_WIRE_DDR &= ~(1 << ONE_WIRE_DQ); // вход
    sei();
    _delay_us(5);
  }
}

/*
 * отправить один байт
 */
void writeByte(uint8_t byte) {
  uint8_t i = 8;
  while (i--) {
    writeBit(byte & 1);
    byte >>= 1;
  }
}

/*
 * получить один байт
 */
uint8_t readByte() {
  uint8_t i = 8, byte = 0;
  while (i--) {
    byte >>= 1;
    byte |= (readBit() << 7);
  }
  return byte;
}

/*
 * получить один бит
 */
uint8_t readBit(void) {
  uint8_t bit = 0;
  cli();
  // логический «0» на 1us
  ONE_WIRE_PORT &= ~(1 << ONE_WIRE_DQ);
  ONE_WIRE_DDR |= (1 << ONE_WIRE_DQ); // вход
  _delay_us(3);

  // освободить линию и ждать 14us
  ONE_WIRE_DDR &= ~(1 << ONE_WIRE_DQ); // вход
  _delay_us(10);

  // прочитать значение
  if (ONE_WIRE_PIN & (1 << ONE_WIRE_DQ)) {
    bit = 1;
  }

  // ждать 45us и вернуть значение
  sei();
  _delay_us(45);
  return bit;
}

/*
 * читать ROM подчиненного устройства (код 64 бита)
 */
uint64_t readRoom(void) {
  uint64_t oneWireDevice;
  if(reset() == 0) {
    writeByte(CMD_READROM);
    //  код семейства
    oneWireDevice = readByte();
    // серийный номер
    oneWireDevice |= (uint16_t)readByte()<<8 | (uint32_t)readByte()<<16 | (uint32_t)readByte()<<24 | (uint64_t)readByte()<<32 | (uint64_t)readByte()<<40 | (uint64_t)readByte()<<48;
    // CRC
    oneWireDevice |= (uint64_t)readByte()<<56;
  } else {
    return 1;
  }
  return oneWireDevice;
}

/*
 * Команда соответствия ROM, сопровождаемая последовательностью 
 * кода ROM на 64 бита позволяет устройству управления шиной 
 * обращаться к определенному подчиненному устройству на шине.
 */
void setDevice(uint64_t rom) {
  uint8_t i = 64;
  reset();
  writeByte(CMD_MATCHROM);
  while (i--) {
    writeBit(rom & 1);
    rom >>= 1;
  }
}

/*
 * провеска CRC, возвращает "0", если нет ошибок
 * и не "0", если есть ошибки
 */
uint8_t crcCheck(uint64_t data8x8bit, uint8_t len) {
  uint8_t dat, crc = 0, fb, stByte = 0;
  do {
    dat = (uint8_t) (data8x8bit >> (stByte * 8));
    for (int i = 0; i < 8; i++) {  // счетчик битов в байте
      fb = crc ^ dat;
      fb &= 1;
      crc >>= 1;
      dat >>= 1;
      if (fb == 1) {
        crc ^= 0x8c; // полином
      }
    }
    stByte++;
  } while (stByte < len); // счетчик байтов в массиве
  return crc;
}

/*
 * поиск устройств
 */
void searchRom(uint64_t * roms, uint8_t & n) {
  uint64_t lastAddress = 0;
  uint8_t lastDiscrepancy = 0;
  uint8_t err = 0;
  uint8_t i = 0;
  do {
    do {
      lastAddress = searchNextAddress(lastAddress, lastDiscrepancy);
      if(lastAddress != DEVICES_ERROR) {
        uint8_t crc = crcCheck(lastAddress, 8);
        if (crc == 0) {
          roms[i++] = lastAddress;
          err = 0;
        } else {
          err++;
        }
      } else {
        err++;
      }
      if (err > 3) {
        return;
      }
    } while (err != 0);
  } while (lastDiscrepancy != 0 && i < n);
  n = i;
}

/*
 * поиск следующего подключенного устройства
 */
uint64_t searchNextAddress(uint64_t lastAddress, uint8_t & lastDiscrepancy) {
  uint8_t searchDirection = 0;
  uint64_t newAddress = 0;
  uint8_t idBitNumber = 1;
  uint8_t lastZero = 0;
  reset();
  writeByte(CMD_SEARCHROM);

  while (idBitNumber < 65) {
    uint8_t idBit = readBit();
    uint8_t cmpIdBit = readBit();

    // id_bit = cmp_id_bit = 1
    if (idBit == 1 && cmpIdBit == 1) {
      return DEVICES_ERROR;
    } else if (idBit == 0 && cmpIdBit == 0) {
      // id_bit = cmp_id_bit = 0
      if (idBitNumber == lastDiscrepancy) {
        searchDirection = 1;
      } else if (idBitNumber > lastDiscrepancy) {
        searchDirection = 0;
      } else {
        if ((uint8_t) (lastAddress >> (idBitNumber - 1)) & 1) {
          searchDirection = 1;
        } else {
          searchDirection = 0;
        }
      }
      if (searchDirection == 0) {
        lastZero = idBitNumber;
      }
    } else {
      // id_bit != cmp_id_bit
      searchDirection = idBit;
    }
    newAddress |= ((uint64_t) searchDirection) << (idBitNumber - 1);
    writeBit(searchDirection);
    idBitNumber++;
  }
  lastDiscrepancy = lastZero;
  return newAddress;
}

/*
 * пропустить ROM
 */
void skipRom() {
  reset();
  writeByte(CMD_SKIPROM);
}

 

Подключение одного DS18B20 к ATmega8

Самый простой способ подключения термодатчика DS18B20 к микроконтроллеру, конечно же, подключение одного датчика. В таком случае нет необходимости искать адрес подключённого датчика, а можем напрямую с ним общаться и считывать данные. Всё это возможно благодаря команды SKIP ROM [CCh] — Пропуск ROM [CCh]. Обратите внимание, что команда ЧТЕНИЕ ПАМЯТИ [BEh] может следовать за командой Пропуска ROM, только если на шине присутствует одно подчиненное устройство. Команда Пропуска ROM, сопровождаемая командой ЧТЕНИЕ ПАМЯТИ вызовет конфликт на уровне данных на шине, если на шине более одного подчиненного устройства, так как все устройства будут пытаться одновременно передавать данные.

main.cpp

#include "config.h"

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "OneWire.h"
#include "LCD.h"


// 123.4
// numbers[0] = 123
// numbers[1] = 4
inline void explodeDoubleNumber(int* numbers, double flt) {
  numbers[0] = abs((int) flt);
  numbers[1] = abs((int) ((flt - ((int) flt)) * 10));
}

inline void printTemp(double d) {
  char text[17] = "T = ";
  int fs[2];
  char num[5];
  
  explodeDoubleNumber(fs, d);
  if (d < 0) {
    strcat(text, "-");
  }
  itoa(fs[0], num, 10);
  strcat(text, num);
  strcat(text, ".");
  itoa(fs[1], num, 10);
  strcat(text, num);
  strcat(text, "'C");
  lcdClear();
  lcdGotoXY(0, 0);
  lcdPuts(text);
}

double getTemp(void) {
  uint8_t temperatureL;
  uint8_t temperatureH;
  double retd = 0;
  
  skipRom();
  writeByte(CMD_CONVERTTEMP);
  
  _delay_ms(750);
  
  skipRom();
  writeByte(CMD_RSCRATCHPAD);
  
  temperatureL = readByte();
  temperatureH = readByte();
  
  retd = ((temperatureH << 8) + temperatureL) * 0.0625;
  
  return retd;
}

int main(void) {
  _delay_ms(100);
  lcdInit();
  lcdClear();
  lcdSetDisplay(LCD_DISPLAY_ON);
  lcdSetCursor(LCD_CURSOR_OFF);

  oneWireInit(PINB0);

  double temperature;

  while (1) {
    temperature = getTemp();
    printTemp(temperature);
    _delay_ms(500);
  }
}

// site: http://micro-pi.ru

double getTemp(void) — возвращает данные температуры в градусах Цельсия.
inline void printTemp(double d) — отображает на экран температуру.
inline void explodeDoubleNumber(int* numbers, double flt) — преобразует вещественное число flt в два целых, которые записываются в numbers.
Вместо функций inline void printTemp(double d) и inline void explodeDoubleNumber(int* numbers, double flt) можно использовать sprintf(), но она жрёт слишком много памяти.

Обычное питание

Подключение одного DS18B20 к ATmega8 (1x1)

Результат

Подключение одного DS18B20 к ATmega8 (1x1 Animating)

Паразитное питание

Подключение одного DS18B20 к ATmega8 (1x1) - Паразитное питание

Результат

Подключение одного DS18B20 к ATmega8 (1x1 Animating) - Паразитное питание

Подключение нескольких DS18B20 к ATmega8

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

Подключение нескольких DS18B20 к ATmega8 на разные выводы

Подключение нескольких DS18B20 к ATmega8 на разные выводы (NxN)

main.cpp

#include "config.h"

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "OneWire.h"
#include "LCD.h"

// 123.4
// numbers[0] = 123
// numbers[1] = 4
inline void explodeDoubleNumber(int* numbers, double flt) {
  numbers[0] = abs((int) flt);
  numbers[1] = abs((int) ((flt - ((int) flt)) * 10));
}

inline void printTemp(double d, uint8_t i) {
  char text[17] = "T[";
  int fs[2];
  char num[5];
  
  itoa(i, num, 10);
  strcat(text, num);
  strcat(text, "]=");
  
  explodeDoubleNumber(fs, d);
  if (d < 0) {
    strcat(text, "-");
  }
  itoa(fs[0], num, 10);
  strcat(text, num);
  strcat(text, ".");
  itoa(fs[1], num, 10);
  strcat(text, num);
  strcat(text, "'C");
  lcdClear();
  lcdGotoXY(0, 0);
  lcdPuts(text);
}

double getTemp(uint8_t pin) {
  uint8_t temperatureL;
  uint8_t temperatureH;
  double retd = 0;
  
  oneWireInit(pin);
  
  skipRom();
  writeByte(CMD_CONVERTTEMP);
  
  _delay_ms(750);
  
  skipRom();
  writeByte(CMD_RSCRATCHPAD);
  
  temperatureL = readByte();
  temperatureH = readByte();
  
  retd = ((temperatureH << 8) + temperatureL) * 0.0625;
  
  return retd;
}

int main(void) {
  _delay_ms(100);
  lcdInit();
  lcdClear();
  lcdSetDisplay(LCD_DISPLAY_ON);
  lcdSetCursor(LCD_CURSOR_OFF);

  double temperature;
  
  uint8_t pin = 0;
  
  while (1) {
    temperature = getTemp(pin);
    printTemp(temperature, pin);
    if (pin == 4) {
      pin = 0;
    } else {
      pin++;
    }
    _delay_ms(500);
  }
}

// site: http://micro-pi.ru

Результат

Подключение нескольких DS18B20 к ATmega8 на разные выводы (NxN Animating)

Подключение нескольких DS18B20 к ATmega8 на одну шину

При подключение нескольких датчиков DS18B20 к ATmega8 на одну шину, главное устройство (микроконтроллер) должно определить коды ROM всех подчиненных устройств на шине. Команда SEARCH ROM [F0h] — (ПОИСК ROM) позволяет устройству управления определять номера и типы подчиненных устройств. Устройство управления изучает коды ROM через процесс устранения, которое требует, чтобы Главное устройство исполнил цикл Поиска ROM (то есть, команда ROM Поиска, сопровождаемая обменом данных). Эту процедуру необходимо выполнить столько раз, сколько необходимо, чтобы идентифицировать все из подчиненных устройств. Если есть только одно подчиненное устройство на шине, более простая команда READ ROM [33h] (Чтения ROM) может использоваться место процесса Поиска ROM.
После каждого цикла Поиска ROM, устройство управления шиной должно возвратиться к Шагу 1 (Инициализация) в операционной последовательности.

Алгоритм поиска 1-Wire устройств с использованием команды Search ROM прекрасно описан в этом видео:

main.cpp

#include "config.h"

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "OneWire.h"
#include "LCD.h"

// 123.4
// numbers[0] = 123
// numbers[1] = 4
inline void explodeDoubleNumber(int* numbers, double flt) {
  numbers[0] = abs((int) flt);
  numbers[1] = abs((int) ((flt - ((int) flt)) * 10));
}

inline void printTemp(double d, uint8_t i) {
  char text[17] = "T[";
  int fs[2];
  char num[5];
  
  itoa(i, num, 10);
  strcat(text, num);
  strcat(text, "]=");
  
  explodeDoubleNumber(fs, d);
  if (d < 0) {
    strcat(text, "-");
  }
  itoa(fs[0], num, 10);
  strcat(text, num);
  strcat(text, ".");
  itoa(fs[1], num, 10);
  strcat(text, num);
  strcat(text, "'C");
  lcdClear();
  lcdGotoXY(0, 0);
  lcdPuts(text);
}

double getTemp(uint64_t ds18b20s) {
  uint8_t temperatureL;
  uint8_t temperatureH;
  double retd = 0;


  setDevice(ds18b20s);
  writeByte(CMD_CONVERTTEMP);

  _delay_ms(750);

  setDevice(ds18b20s);
  writeByte(CMD_RSCRATCHPAD);

  temperatureL = readByte();
  temperatureH = readByte();

  retd = ((temperatureH << 8) + temperatureL) * 0.0625;

  return retd;
}

int main(void) {
  _delay_ms(100);
  lcdInit();
  lcdClear();
  lcdSetDisplay(LCD_DISPLAY_ON);
  lcdSetCursor(LCD_CURSOR_OFF);
  
  oneWireInit(PIND7);
  
  double temperature;
  uint8_t n = 8;
  uint64_t roms[n];
  searchRom(roms, n);
  char txt[17] = "devices [";
  char num[5];
  itoa(n, num, 10);
  strcat(txt, num);
  strcat(txt, "]");
  lcdClear();
  lcdGotoXY(0, 0);
  lcdPuts(txt);
  _delay_ms(2000);
  while (1) {
    for (uint8_t i = 0; i < n; i++) {
      temperature = getTemp(roms[i]);
      printTemp(temperature, i + 1);
      _delay_ms(500);
    }
  }
}
// site: http://micro-pi.ru

Обычное питание

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN)

Результат

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating число найденных устройств)

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating 1)

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating 2)

Паразитное питание

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN) - Паразитное питание

Результат

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating число найденных устройств) - Паразитное питание

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating 1) - Паразитное питание

Подключение нескольких DS18B20 к ATmega8 на одну шину (1xN Animating 2) - Паразитное питание

Внимание! При использовании паразитного питания не рекомендуется использовать датчики для температуры выше +100ºC, а также при операциях преобразования температуры и копирования данных из Scratchpad в один из регистров EEPROM, потребляемый ток может достигать 1.5 мА, что непосильно внутреннему конденсатору, а на резисторе подтяжки (R1 4.7k) будет большое падение напряжения, что недопустимо скажется на работе устройства в целом. Для этого необходимо организовать линии DQ схему мощной подтяжки, реализуемой по такой схеме:

DS18B20 вариант подключения - так называемое паразитное подключение (паразитное питание)

После выдачи команды конвертирования температуры [44h] или копирования ОЗУ В ПЗУ (Copy Scratchpad) [48h] необходимо включить мощную подтяжку MOSFET-транзистором линии DQ на 10 мкс (макс.), как указанно в даташите датчика, после чего выждать время преобразования или время передачи данных, причем в это время никаких действий при включенной мощной подтяжке на линии DQ быть не должно!

Скачать Atmel Studio 7 проекты и схемы в ISIS Professional (Proteus):
DS18B20 + LCD 16×2 + ATmega8 — ISIS Professional (Proteus)
DS18B20 + LCD 16×2 + ATmega8 — Atmel Studio 7

Купить DS18B20 на Aliexpress


Купить ATMEGA8A на Aliexpress


Купить LCD1602 HD44780 на Aliexpress

Похожие записи

Комментарии 132

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

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