Цифровой термометр на ATMega8

AVR
Добрый день! Хочу поделиться с сообществом своей реализацией цифрового бытового термометра на контроллере ATMega8 и датчиках DS18x20.

Вступление
Немного предистории…
Это не первая конструкция электронного термометра, собранного мною. Несколько ранее (когда я был знаком только с PIC контроллерами) я собрал бытовой термометр на PIC16F628, датчиках DS18S20 и 2-х строчном ЖК индикаторе на основе контроллера HD44780. Программу писал, на C с использованием среды PIC C Compiler. Использовал готовые библиотеки кода для общения с датчиками и LCD дисплеем. Электронную схему выполнил на макетке, все детали в DIP корпусах.

Предыдущая версия термометра

Этот термометр до сих пор исправно работает (установлен на кухне) и показывает температуру «за бортом» и на балконе.
Спустя некоторое время, после знакомства с AVR и освоения ЛУТ технологии в связке с SMD компонентами у меня и зародилась идея создать более компактную и более дешевую версию такого цифрового термометра.
Вот что из этого получилось.

Постановка задачи
Исходное задание звучит так:
  • Измерение температуры датчиками DS18B20 и DS18S20 одновременно.
  • Подключения нескольких датчиков на одну шину 1-Wire для многоточечных измерений температуры.
  • Дешевый индикатор, видимый в темноте.
  • Компактный корпус.
  • Удобство подключения проводов от датчиков и источника питания.
  • Внешний источник питания 5В для простоты реализации.
  • Отображение целого значения температуры в °С (т.к. у нас всего 2 разряда на индикаторе).
Итак, условие о применении любых датчиков серии DS18x20 возникло из-за того, что у меня в наличии как раз остались один DS18S20 и один DS18B20. Как выяснилось, покопавшись в сети, прошивок термометров, работающих одновременно с этими двумя датчиками нету. (возможно плохо искал, но тем не менее). Почитав даташиты на эти датчики, стало понятно, что представление температуры в них выполнено по разному. Значит будем создавать свой универсальный код микроконтроллера! Об этом подробнее в программной части.
Выбор дешевого индикатора, который бы еще и светился сводится, конечно же, к применению семисегментного. Тут альтернативы нет.
Выбор компактного корпуса у меня занял некоторое время. Свой выбор я остановил на обычной телефонной евророзетке с двумя разъемами RJ11.

Конструкция
Схема электрическая принципиальная:

Схема электрическая принципиальная



Размеры телефонной розетки наложили жесткие ограничения на размер печатной платы. Поэтому всю разводку электрической схемы на дорожках выполнить не удалось — пришлось применять некоторое количество коротких отрезков провода МГТФ. Также я отказался от применения внешнего кварцевого резонатора и использовал внутренний RC генератор 1МГц.
Два разъема RJ11 служат для подключения внешнего источника питания и выносного датчика температуры DS18x20. Причем оба разъема равнозначны и их нельзя перепутать, т.к. цепи питания и шина 1-Wire разнесены на разные контакты (получилась небольшая защита от «дурака»). Блок питания и выносной датчик (можно и не один) подключаются обычными телефонными 4-х жильными проводами с разъемом RJ11. Датчики DS18x20 необходимо хорошенько изолировать от воздействия окружающей среды при работе на открытом воздухе. В своей конструкции я вывел 2-ой датчик температуры из корпуса прибора наружу на коротком проводнике (измерение температуры в комнате).
Далее приведу фото получившейся конструкции:







Для индикации минусовой температуры служит отдельный светодиод. Первоначально я хотел поставить прямоугольный советский светодиод (не помню марку, вроде КИПМО какой-то), но мощность излучения оставляла желать лучшего… Поэтому я его заменил на SMD светодиод красного свечения:



Программная часть
Код писался в CV AVR. Использовал библиотеку «ds18b20.h». Использовалась динамическая индикация. Переключение между индикаторами происходит программно по прерыванию таймера.
Полностью приводить код не буду. Покажу только главный цикл:


// void MakeStr(unsigned char val1, unsigned char val2) - это процедура отображения 
//  2-х символов на 2-х сегментах (по коду символа).
// Например, MakeStr(17, 17); - вывод 2-х пустых символов (погасание индикаторов).
// void MakeBuf(signed char val) - процедура записи в буфер отображения значения
//  температуры (и отрицательной  с автоматическим включением светодиода "минус").
//
// unsigned char rom_code[MAX_DEVICES][9]; здесь хранятся ROM коды найденных 
//   1-Wire устройств.
//
 while (1)
     { 
       if (devices <= 0)
       {
         //------- ищем 1-wire устройства --------------- 
         // запрещаем прерывания
         #asm("cli");         
         devices=w1_search(0xf0,rom_code); 
         // разрешаем прерывания
         #asm("sei"); 
       }
       // если не нашли, то выводим '--'
       if (devices == 0) 
       { 
         MakeStr(16, 16); 
         delay_ms(2000);  
         continue;   // ищем датчики заново    
       } 
                         
          // забираем температуру
         
           for (i=0;i<devices;i++)
           {   
             // преобразуем и достаем температуру
             err2 = 0;
             
            // Выводим поочередно t1, t2... если более двух датчиков
             if (devices > 1)
             {   
               MakeStr(17, 17);
               delay_ms(1000); 
               MakeStr(18, i + 1);
               delay_ms(2000);  
               MakeStr(17, 17);
              // delay_ms(500); 
             } 
             
             // запрещаем прерывания
             #asm("cli");             
             // выбираем датчик по адресу     
             if (!ds18b20_select(&rom_code[i][0])) err2 = 2;  
             // запускаем преобразование температуры
             w1_write(0x44);  
             
             // разрешаем прерывания
             #asm("sei");   
             // ожидаем преобразования 
             delay_ms(1000);
             
             // запрещаем прерывания
             #asm("cli"); 
             // читаем регистры памяти
             if (!ds18b20_read_spd(&rom_code[i][0])) err2 = 3; 
                          
             // разрешаем прерывания
             #asm("sei"); 
             // Если не было ошибок, то вычисляем далее температуру
             if (! err2) 
             {   
                // формируем число int из двух байтов  
                aint = __ds18b20_scratch_pad.temp_msb;
		aint = aint << 8;
		aint |= __ds18b20_scratch_pad.temp_lsb;
               // если датчик DS18S20
               if (rom_code[i][0] == 0x10)
               {
                 temp = aint / 2; // только для ds18S20
                 if ((aint % 2) > 5) // округление
                 {                  
                    temp++;
                 }
               }
               // если датчик DS18B20
               if (rom_code[i][0] == 0x28)
               {
                 temp = (int) aint * 0.0625; // только для ds18B20
               }
                              
               // если темп. отрицательная
               if (temp > 1000)
               {
                 temp = 4096 - temp;
                 temp = -aint;
               }
               // выводим температуру 
               strbuf = temp;
               MakeBuf(strbuf);
             } 
               else // если были ошибки, то выводим код ошибки
               {
                 MakeStr(19, err2);  
               }            
             // задержка инфы на дисплее  
             delay_ms(4000); 
           };
     } 
}


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

Общий алгоритм вычисления температуры таков:
  1. Ищем все устройства на шине 1-Wire, используя команду w1_search(0xf0,rom_code); Функция возвратит количество найденных устройств, а в массиве rom_code будут храниться их серийники (каждое устройство на шине 1-Wire имеет свой уникальный серийный номер, используя который можно обращаться индивидуально к конкретному экземпляру).
  2. В цикле проходимся по каждому из найденных датчиков температуры. И проделываем с ними нижеследующее
  3. Посылаем на шину адрес датчика с которым будем общаться. ds18b20_select(&rom_code[i][0]). Теперь дальнейшие команды на шине 1-wire будет воспринимать только выбранный нами датчик с серийником rom_code[i].
  4. Посылаем на шину команду начала преобразования температуры w1_write(0x44). См даташит на DS18x20
  5. Ожидаем 1с. т.к. нашему датчику нужно время на преобразование температуры. delay_ms(1000); См. даташит.
  6. Считываем содержимое из датчика. ds18b20_read_spd(&rom_code[i][0]). Значение температуры хранится в двух байтах (старшем и младшем) Вот их значения мы и считываем. Они появятся у нас в структуре __ds18b20_scratch_pad.temp_msb и __ds18b20_scratch_pad.temp_lsb.
  7. Далее склеим эти два байта в одну переменную типа int, используя сдвиг. Теперь значение температуры у нас будет находиться в единственной переменной aint.
  8. Вот тут и наступает момент Х. У датчика DS18B20 температура храниться (по умолчанию) с разрешением 12 бит. Т.е. каждой 1 регистра температуры будет соответствовать 0,0625 °С, а датчик DS18S20 имеет разрешение 9 бит и каждой 1 регистра соответствует 0,5 °С. Поэтому далее мы должны проанализировать с каким датчиком имеем дело if (rom_code[i][0] == 0x10). Мы анализируем первую цифру серийника нашего датчика. В этой цифре и закодирован код семейства датчика (0x10 для DS18S20 и 0x28 для DS18B20).
  9. Поняв теперь с каким датчиком имеем дело заканчиваем вычисление температуры. Для DS18S20 просто делим число на 2. ( temp = aint / 2). Для DS18B20 умножаем число на 0,0625 ( temp = (int) aint * 0.0625).
  10. На последнем шаге алгоритма нам нужно проверить, не является ли температура отрицательной и если да, то нужно преобразовать полученное значение температуры из дополнительного кода в тип int со знаком. Т.е. temp = 4096 — temp; temp = -aint;
  11. Все. Теперь в переменной temp хранится целое значение температуры в градусах цельсия со знаком.

Для прошивки контроллера я использовал программатор usbasp с оболочкой Khazama AVR Programmer (В данной оболочке фьюзы отличаются (1 и 0) от PonyProg, например)
Для Khazama AVR Programmer такие фьюзы:
H Fuse: 11001111
L Fuse: 11100001
1 — это галочка ПОСТАВЛЕНА;
0 — галочка СНЯТА.
А вообще, чтобы не заморачиваться сильно методика такая:
Сначала читаем фьюзы по умолчанию из нового микроконтроллера, потом выставляем внутренний генератор на 1 МГц в качестве тактирования. И все должно прошиться успешно.

Для более детального изучения работы с температурными датчиками DS18x20 обратитесь к даташитам. Также имеется пример работы с ними в среде CV AVR.

Заключение
Демонстрация работы:

Все замечания и предложения приветствуются. Надеюсь, для кого-нибудь данная статья окажется полезной.
Также, привожу все файлы (схема, плата, исх. код) проекта.
Даташит на DS18S20.
Даташит на DS18B20.

Использованные источники:

1. Алгоритм динамической индикации.

p.s. На видео и некоторых фото не видно второго датчика температуры. Первоначально он был внутри корпуса, позже я вывел его наружу для более точного измерения температуры окружающего воздуха.
Файлы в топике: 7Seg_Termometr.zip, 18S20.PDF, DS18B20.PDF

Комментарии (47)

RSS свернуть / развернуть
Честно говоря, наиболее бюджетное из виденных мной решение — купить у китайцев. Термометр в сборе стоит как один DS18B20. Ну, если покупать локально — как два.
Алсо, почему-то никто не лепит термометры на термисторах. Везде 18В20 и подобные цифровые датчики. Видимо, потому что не требуется включать мозги (и калибровать заодно).
Алсо, светодиодный индикатор и сетевое питание… Беее.
0
  • avatar
  • Vga
  • 07 октября 2011, 20:38
Даешь совковую термопару в виде штанги метровой длинны торчащей из окна! :D
0
таки там платина, долго торчать не будет…
0
Да нуууу, мерять сопротивление термистора…
Меня максимум хватило на оцифровку выхода LM35. После этого пришел мешок LM75, а там i2c со своей простотой :)
0
Видимо, потому что не требуется включать мозги (и калибровать заодно).

Ну, мозги там тоже включать особо не надо. А вот калибровать — да, меня останавливает именно это.

Кстати, я тут тоже собираюсь термометр делать. На LM75A. :D

«В жизни каждого эмбеддера наступает момент, когда он понимает, что ему пора сделать термометр...»

:D
+1
И часы! Точнее часы с термометром :)
0
Конечно! Это своего рода обряд уже. :)
0
светодиодный индикатор и сетевое питание… Беее.
А как вы представляете это? На батарейках? Ведь прибор стационарный, режим работы 24/7
0
Если не критична яркость и применить ЖК, то может годами от батареек работать :)
0
stm8 и сегментный жк индикатор. Потребление будет равно потреблению датчика + 10...50мкА:)
0
можно и солнечную батарею прикрутить и в космос отправить…
Обсуждаем здесь конкретную реализацию на светодиодных индикаторах
0
А светодиодные индикаторы обязательное условие?
0
Нет, конечно, просто для меня было критично чтоб было видно в темноте и не дорого
0
Да ну, все ОК. Я тоже буду на светодиодных делать. Потому что светятся.
0
Если надо светиться, то выбора особого нет. ЛЦД 16х2 и графические выглядят убого. На ТФТ можно гламурненько порисовать, но от батареек питание все равно не экономично будет.
0
Есть еще VFD. А «экономично/от батареек» и «светиться» — в принципе несовместимо, увы.
Разве что для свечения применять отдельный источник энергии, вроде СПД)
0
Именно поэтому и упомянул их разом. Хотя некто на радиокоте делал и на батарейках. Ценой, разумеется, кнопки «показать температуру». Я разделяю мнение Ди «розетка — стратегический ресурс», да и не особо верю китайским зарядкам в режиме 24/7.

«В жизни каждого эмбеддера наступает момент, когда он понимает, что ему пора сделать термометр...»
Ну дык, хоть бы с блэкджеком и шлюхами. А так — аналог обычного китайца.
0
temp = (int) aint * 0.0625

wtf? О_о
temp = aint/16 или вообще temp = aint>>4

а это:
if (temp > 1000)
               {
                 temp = 4096 - temp;
                 temp = -aint;
               }

вообще как понимать?
0
по поводу temp = (int) aint * 0.0625 согласен, сдвигом будет менее накладно, но я код не оптимизировал, т.к. для одной задачи измерения температуры это и не нужно.

А второе — это проверка является ли температура отрицательной, если да, то проводим преобразование из дополнительного кода представления отрицательного числа в человеческий вид, так сказать
0
1. по поводу причесывания кода я не заикался, просто расчёт с точкой, а потом преобразование в целое на МК это вообще веселье

2.1 точно точно перевод?
подряд два присвоения одной переменной

2.2 да и вообще, если я правильно понимаю код, то тут обращать нужно ещё бинарное представление, а не уже преобразованное в температуру
0
Товарищ en1gma правильно заметил:
temp = 4096 - temp;
                 temp = -aint;

Первая операция присваивания бессмысленна=)
0
Да есть косяк такой. Достаточно temp = -aint
0
А зачем его переводить-то? Дополнительный код же самый что ни на есть нативный для МК. Не то что некоторые датчики со странным представлением отрицательных чисел (хотя возможно я там просто не вчитывался и это по уродски описанный дополнительный код).
0
А зачем на датчики три провода? Можно же запитать через паразитное питание.
0
С паразитным питанием много датчиков на одну шину не посадишь, да и жестче ограничение по длине шины связи. К тому же я использовал стандартный телефонный провод с разъемами RJ11, а в нем 4 жилы. В общем не рекомендую паразитное питание, если будете использовать длинный провод и несколько датчиков.
0
У меня в лоджии стоит почти аналогичный термометр тоже на 8 меге и двух датчиках. Два датчика висят на 5 метровом проводе 2*0.5. Оба прекрасно себя чувствуют на паразитном питании даже без принудительной подтяжки с помощью ключа, как рекомендуют в даташите.
0
у меня термо-резюк валяется на 4,7к, сделать делитель и на ацп арма, у будет термометр на арме. :-)
0
а какой у него ТКС? Мне кажется, что с делителем получится генератор случайных чисел на ARMе
0
Китайцы так и делают. И вполне нормально меряют. Так что способ вполне рабочий.
0
DS18B20 можно зацепить на по 3ем проводам даже на 50метров и более. (Только надо сигнал ему через транзистор давать), а терморезюк на такой дистанции погрешность будет давать
0
До 300 вроде. Но по большей части это не очень нужно. А на метровом хвосте и термистор норм. Тут тож не километровый хвост.
0
Только тогда надо контрольные суммы проверять, а то температура в -370 будет вполне привычным делом:)
0
Вопрос тем кто использует подобные термометры для определения температуры за бортом квартиры: как вы вытаскиваете датчик на улицу, если нет балкона? Просто засовываете пучок проводов между рамой и створкой окна или есть более удобные методы?
0
  • avatar
  • rad
  • 09 октября 2011, 15:45
У меня на кухне, где градусник, пластиковые окна. Соответственно ни просверлить, ни толстый провод прижать. Уплотнитель довольно твёрдый, не хотелось бы попортить. Сделал плоским шлейфиком, забыл как они называются. ЖК дисплеи на таких часто бывают. Выдрал из старого сидирома. Если ещё в белый цвет шлейф покрасить — вообще незаметно будет.
0
Спасибо огромное за совет, имеете ввиду такой шлейф?
0
Ага, он самый!
0
Я примазался с термометром при установке пластиковых окон. Так что провод уходит прямо в стену)

В принципе, еще можно продырявить раму, благо она пластиковая, а затем замазать дырки клеем. Кроме того, с внешней стороны уже есть дырки, для стока воды из углублений рам.
Можно проколупать пену вокруг рамы, пропихнуть там и снова замазать.
0
Я взял IDE шлейф от компа, разорвал чтобы получились трехжильные провода, соединил последовательно, чтобы длина была побольше (получилось метров 10) и провел через пластиковое окно, ничего не сверлил, просто открыл окно, приклеил провод с обоих сторон рамы(чтобы не болтался), работает безотказно около 3х лет
0
а кстати как насчет нагрева датчика солнцем, или остужения ветром/дождём? иногда можно градусов на 5 наколоться.
предусматриваете какую-нибудь «защиту» от всего этого?
0
Ветер не остужает. Ветер ускоряет выравнивание температур датчика и воздуха и уменьшает перегрев датчика солнечным излучением.
Насчет дождя не задумывался. В принципе, скорее всего дождь тоже имеет температуру воздуха.
А вот солнце это да, если датчик на солнце — из показаний можно смело минусовать 10-20 градусов, а то и больше.
Лучшая защита — несколько датчиков и смотреть по тому, который в тени. Плюс я спрятал датчик под внешний подоконник/козырек (не помню, как он там называется). Не идеал, но от прямого попадания дождя/солнца защищает.
0
Говорят, что от прокладки напрямую проводов может пострадать уплотнитель окна…

Да, и еще вопрос: как правильно заизолировать датчик от дождя и других осадков например? термоусадкой можно(наверно желательно белую?)? и про солнце что-то я изначально не подумал, под карниз можно спрятать, конечно, этого хватит?
0
Эпоксидкой залей. У китаецев вроде уже изолированный, из коробки.

Козырек немного улучшит, но в целом — нет, верить датчику с солнечной стороны нельзя. Надо ставить несколько с разных сторон.
0
Имею опыт работы с этими датчиками и вообще, с сетью 1-wire. Когда то давно сравнивал терморезистор, термодиод, термостабилитрон и DS18x20. Терморезистор отбросил почти сразу, следом за ним забил на термодиод. Остались трехногий термостабилитрон(LM* какой-то) и DS18x20, цена второго в 2 раза выше, НО… Стабилитрон далеко не проведешь, если увеличить длину кабеля, то нужно учитывать его сопротивление, еще надо использовать шумящий АЦП, также, в некоторых случаях, большой недостаток — их нельзя соединить параллельно. DS18x20 лишен всех этих недостатков, порой пугает цена, но если покупать оптом, то получается не так и дорого (раза в 1,6-1,8 дороже стабилитрона). Еще меня в этих датчиках отпугивало использование 1-wire, который требует очень точных задержек (ресет не считаю), но потом оказалось, что 1-wire можно эмулировать через UART, что я и сделал, могу сказать, что этот вариант самый безглючный, у меня 1-wire через usart работает на atmega8, с подключенными 15 датчиками DS18b20, плюс на том же устройстве работает софтовый USB, который хост опрашивает каждые 100 мс.
Еще советую подключать 1-wire через транзисторы, но если у вас на шина не больше 20 метров и на шине не более 3 датчиков, то можете обойтись одним резистором.
0
Да, действительно с UART отлично работает. Сам однажды собирал термометр для PC по статье. Отлично работает, без сбоев.
А у меня вопрос еще к MrMisha, Вы писали про Мегу, работающую по USB. Вы использовали libusb или просто на Меге был собран конвертер USB-UART? На PC вы получали данные через виртуальный COM порт или делали опрос меги как положено, через драйвера USB? Очень интересно было бы заюзать USB в будущих проектах.
0
kibermaster.net/mnogokanalnyiy-usb-termometr/ вот сдесь все написано.
Делал через опрос. Вариант с виртуальтым СОМ портом (CDC) занимает примерно 80-90% процессорного времени, это только сам юсб, а ведь МК еще что то должен делать))
0
Каким программатором прошивал и как правильно выставить фузы
0
  • avatar
  • vltab
  • 12 октября 2011, 09:00
Программатор usbasp с оболочкой Khazama AVR Programmer (В данной оболочке фьюзы отличаются (1 и 0) от PonyProg, например)
Для Khazama AVR Programmer такие фьюзы:
H Fuse: 11001111
L Fuse: 11100001
1 — это галочка ПОСТАВЛЕНА;
0 — галочка СНЯТА.
А вообще, чтобы не заморачиваться сильно методика такая:
Сначала читаем фьюзы по умолчанию из нового микроконтроллера, потом выставляем внутренний кварц на 1 МГц в качестве тактирования. И все должно прошиться успешно.
0
зачетно :) я тоже хочу подобное сделать, пока что меня хватило только на один датчик DS18b20 и передачу через COM-порт в комп ;)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.