Шина 1-wire, пример термометра на DS18B20 и микроконтроллере AVR, WH1602, ЖКИ

Шина 1-Wire была запатентована компанией Dallas Semiconductor, и была призвана наладить полудуплексную связь всего по одному сигнальному проводу. Также возможны варианты использования паразитного питания по линии данных (потому что во все микросхемы 1-Wire встроен конденсатор номиналом 800пФ).

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

В конце концов, погоняв код в VMLab понял, что эта шина очень чувствительна к таймингам и к номиналу резистора, подтягивающего линию данных. Первым результатом успешной работы с 1-Wire у меня стал цифровой термометр на базе датчика DS18B20 и микроконтроллера ATmega8, данные о температуре выводились на символьный ЖКИ WH1602A. О чем я хочу написать заметку.
Рассмотрим шину 1-Wire поподробнее.

Каждое устройство 1-Wire имеет уникальный ROM код, так называемый адрес. На шине должно быть одно ведущее устройство (master) и одно или несколько ведомых (slave). Количество ведомых ограничено адресным пространством и емкостью шины. При начале обмена данных, ведущий должен проверить наличие на шине хоть кого-то. Для этого мастер на протяжении 480мкс должен удержать логический ноль на шине (так называемый Reset pulse), «отпустить» шину минимум на 60мкс и посмотреть какой уровень присутствует на линии данных. Если это низкий уровень – на шине есть устройство 1-wire. Если высокий – шина пуста. Это так называемый Presence pulse.
присутствие DS18B20
Далее следует передать байт-команду устройствам. Для этого ознакомимся с принципом передачи данных по 1-Wire. Передача байта разбита на так называемые временные слоты (60-120мкс), один временной слот служит для передачи одного бита. Данные передаются последовательно, начиная с младшего бита. Для передачи битов от ведущего к ведомому используется верхняя (на картинке) схема:
чтение/запись DS18B20
Для передачи логического 0, ведущий должен удержать на шине нулевой потенциал на протяжении всего временного слота (60-120мкс), а после отпустить шину, для сигнализации окончания временного слота. Для передачи логической 1, ведущий должен удержать шину в нулевом положении чуть более 1мкс (но не больше 15мкс), а после отпустить шину на весь оставшийся временной слот.

Для передачи данных в обратном направлении, от ведомого в ведущему используется нижняя (на картинке) схема. Микроконтроллер удерживает низкий уровень на протяжении чуть более 1мкс, после чего отпускает шину и ждет примерно 30мкс и проверяет состояние на шине, если там высокий уровень – то передана логическая единица, если низкий – то передан логический ноль.
В принципе все просто, но вот с этими задержками и возникают трудности. Теперь рассмотрим схему, по которой можно выжать данные о температуре из DS18B20. Сначала проверим наличие DS18B20 на шине с помощью Presence pulse. После дадим команду 0хсс (игнорировать ROM код, т.е. все следующая команда предназначена всем ведомым на шине) и 0х44 (начать преобразование), после чего выжидаем пока датчик преобразует температуру в код и после команда 0хсс, 0хbe (выдать температуру) принимаем 2 байта кода температуры. Вообще такой подход не универсален, если на шине присутствует несколько устройств, то получиться каша. Для того, чтобы не было каши, используют ROM код, и устройство откликается только на свой ROM. В случае с iButton, ROM код написан прямо на корпусе устройства. С DS18B20 такого не наблюдается и приходиться считывать ROM с помощью команды 0х33. Но возникает проблема, как считать ROM код, если на шине 2 устройства и не один из их ROM неизвестен. Для этого существует специальный алгоритм, про который можно почитать в даташите DS18B20. Здесь его описывать не буду, потому что у меня только одно устройство 1-wire на шине, а сам алгоритм громоздкий и писать его без цели не хочется. Как разживусь на 2-й датчик DS18B20 – так и напишу про алгоритм нахождения ROM кодов 
Для работы с 1-wire предлагают частично использовать аппаратный UART, но так не спортивно, тратить ценный UART на какой-то 1-Wire, который можно реализовать на любой ножке микроконтроллера?? Поэтому напишем функции работы с 1-Wire, привязанные к заданной ножке порта, например к PB0. Только сперва рассмотрим схему подключения DS18B20 к микроконтроллеру ATmega8.

Микроконтроллера тактируется от внутреннего RC генератора на 4МГц. Резистор R1 – подтягивающий на 6,8кОм, R2 – ограничительный на 1кОм, R3 – 17 Ом, потенциометр VR1 – 10кОм. Конденсаторы С5,С7 – электролиты, по 100 и 200мкф соответственно. С4,С6 – керамика на 0.1мкф.
Теперь рассмотри функции работы с DS18B20:

  1. #define MASK 0x01
  2. unsigned char present_ds18b20(void)
  3. { unsigned char res;
  4.  
  5. DDRB|=MASK;
  6. _delay_loop_2(475); //Pause 480mks
  7.  
  8. DDRB&=~MASK;
  9. _delay_loop_2(65); //Pause 70mks
  10.  
  11. if ((PINB&MASK) == 0x00) res=1; //if present, res=1
  12. else res=0; // else 0
  13. _delay_loop_2(405); //pause 410mks
  14. return res;
  15. }
  16.  
  17. void send_ds18b20(unsigned char command)
  18. { unsigned char i, data;
  19.  
  20. data=command;
  21.  
  22. for(i=0;i<8;i++)
  23. {
  24. if ((data&0x01)==0x01) { //Send 1 on SDA
  25. DDRB|=MASK;
  26. _delay_loop_2(2); //pause 6mks
  27. DDRB&=~MASK;
  28. _delay_loop_2(60);//pause 64mks
  29. }
  30. else { //Send 0 on SDA
  31. DDRB|=MASK;
  32. _delay_loop_2(55);//pause 60mks
  33. DDRB&=~MASK;
  34. _delay_loop_2(2);//pause 10mks
  35. }
  36. data=data>>1;
  37. }
  38. }
  39.  
  40. void receive_ds18b20(void)
  41. { unsigned char i;
  42.  
  43. temperature_sign=0;
  44. for(i=0;i<16;i++)
  45. {
  46. DDRB|=MASK;
  47. _delay_loop_2(2); //Pause 6mks
  48. DDRB&=~MASK;
  49. _delay_loop_1(6); //Pause 9mks
  50.  
  51. if ((PINB & MASK)==0x00) temperature&=~_BV(i);//If 0 on SDA
  52. else {
  53. temperature|=_BV(i); //IF 1 on SDA
  54. if (i==12) temperature_sign=1;//IF its sign
  55. }
  56. _delay_loop_2(50); //Pause 55mks
  57. }
  58. }

Здесь я не использовал привычную конструкцию _BV(SDA), потому что вычисление результата функции занимает время и влияет на формируемые задержки. Поэтому использую константу MASK. Думаю что назначения функций рассказывать не стоит, названия говорят сами за себя. Фрагмент обмена данными (Presence pulse и команды 0хсс, 0х44), захвачен с помощью моего логического анализатора:

Работу же самого термометра можно просмотреть на видео:

Скачать полный код проекта для AVR Studio 4.

Достать можно собственно

Достать можно собственно температуру и 2 регистра коррекции (T Register for user byte) и CRС + configuration register для задания точности.

Посылать команды по 1-wire, я юзаю функцию void send_ds18b20(unsigned char command).

А можешь оформить библиотеку

А можешь оформить библиотеку для удобства использования?

Как появится свободное время

Как появится свободное время - оформлю.

Думаю что и самому ее несложно сделать, пару функций копи-пастом + заголовки + чего-то своего полезного дописать.

А как сделать следующее: При

А как сделать следующее: При приходе с ПК например 1(0х31) МК начинает передавать значения температуры по RS232. При приходе 0(0х30) передача останавливается.

Пример моего кода, работает

Пример моего кода, работает на прием 1 и 0 по юарту.
Выполняет определенные действия по приходу этих байтов.

  1. ISR(USART_RX_vect)
  2. {
  3. int b;
  4. b = UDR;
  5.  
  6. if (b=='1')
  7. {USART_Transmit('y');}
  8. else
  9. if(b=='0')
  10. {USART_Transmit('n');}
  11. }

Сделай прерывание по приему

Сделай прерывание по приему байта от UART.
В этом прерывании глобальная переменная char flag=UDR;

Потом в том месте, где выводится данные на дисплей ЖКИ встав

  1. if (flag==0x31) {
  2. вывести на UART и ЖКИ
  3. }
  4. else {
  5. Выыести на ЖКИ только
  6. }