mikroPascal for AVR. Урок 5. Использование OneWire. Встроенная библиотека

Это уже наш 5 урок по mikroPascal for AVR, и в нем будет рассмотрен интерфейса OneWire, применительно к среде разработки mikroPascal. А точнее, встроенная библиотека «One_Wire».

Скрин(1_Wire)

Как вы наверное уже догадались, слово «встроенная» тут ничего хорошего не предвещает, а именно, доступно всего три процедуры: сбросить линию (ow_reset), отправить байт (ow_write) и принять байт (ow_read). Вот собственно и все,видимо разработчики посчитали что этого должно хватить. Да, для базовых операций этого как раз таки хватает, но не более. В качестве наглядного пособия будем использовать DS18B20, ведь многие будут собирать свой первый цифровой термометр именно на нем (или уже собрали 🙂 ).

И так, начнем с самого начала. Что такое 1-wire? 

1-Wire  — двунаправленная шина связи для устройств с низкоскоростной передачей данных, в которой данные передаются по цепи питания (то есть всего используются два провода — один для заземления, а второй для питания и данных; в некоторых случаях используют и отдельный провод питания). Разработана корпорацией Dallas Semiconductor (с 2001 года — Maxim Integrated).

То есть, на одном проводе может быть большое количество различных устройств. Кроме того, большинство устройств 1-Wire поддерживает т.н. «паразитное питание» (имеют префикс «PAR»). Соответственно, для радиолюбителей они привлекательны по той причине, что для нескольких датчиков нужна всего 1 нога микроконтроллера. Полный разбор 1-Wire можно найти в поиске на сайте, или по ссылке: http://cxem.net/comp/comp53.php

Так как в этом уроке цель — поработать с библиотекой, то рассмотрим следующие ситуации: датчик всего один, датчиков много (на разных пинах), датчиков много (на одном пине), запись / чтение конфигурации в память датчика.

Начнем с одного датчика. 

Последовательность команд можно легко найти в интернете, вот она:

  1. Посылаем команду сброса;
  2. Посылаем общую команду;
  3. Посылаем функциональную команду;

Вот схема (одинакова для всех примеров) и код :

схема

 program cxem_net_1_Wire;  var LCD_RS : sbit at PORTC0_bit;                   //Подключаем LCD var LCD_EN : sbit at PORTC1_bit; var LCD_D4 : sbit at PORTC2_bit; var LCD_D5 : sbit at PORTC3_bit; var LCD_D6 : sbit at PORTC4_bit; var LCD_D7 : sbit at PORTC5_bit;  var LCD_RS_Direction : sbit at DDC0_bit; var LCD_EN_Direction : sbit at DDC1_bit; var LCD_D4_Direction : sbit at DDC2_bit; var LCD_D5_Direction : sbit at DDC3_bit; var LCD_D6_Direction : sbit at DDC4_bit; var LCD_D7_Direction : sbit at DDC5_bit;  var t: string [6];                                 //Эта переменная будет служить                                                    //для вывода информации на дисплей function ReadTemp: string [6]; var a: array [0..1] of byte;                       //Вообще то можно обойтись и без массива     b: integer;                                    //но с ним как-то красивее выходит + переменная для промежуточного хранения результата begin      ow_reset(PORTB, 0);                           //Сброс 1-Wire      ow_write(PORTB, 0, $CC);                      //Посылаем команду Skip Rom (обращение ко всем устройствам на линии)      ow_write(PORTB, 0, $44);                      //Далее говорим датчику, что неплохо бы начать конвертацию температуры      delay_ms(750);                                //Пауза на время конвертации (нужно смотреть на разрешение датчика)      ow_reset(PORTB, 0);                           //Снова сброс      ow_write(PORTB, 0, $CC);                      //      ow_write(PORTB, 0, $BE);                      //Команда чтения ROM      delay_us(120);                                //Задержка не обязательна      a[0] := ow_read(PORTB, 0);                    //Считываем старший байт,      a[1] := ow_read(PORTB, 0);                    //потом младший и приводим к      b := ((a[1] shl 8) + a[0]) shr 4;             //нормальному виду.      if b > 1000 then b := -(4096 - b);            //      IntToStr(b, result);                          //Возвращаемое значение end;  begin                                              //      lcd_init;                                     //Инициализация LCD      lcd_cmd(_LCD_CURSOR_OFF);                     //Отключаем курсор (что б не мигал)      While TRUE do begin                           //            lcd_cmd(_LCD_CLEAR);                    //            lcd_out(1, 1, 'Temp:');                 //            t := ReadTemp;                          //Обращаемся к функции ReadTemp            lcd_out(1, 6, t);                       //Выводим полученное значение на LCD            delay_ms(2000);                         //      end; end.

Код на мой взгляд достаточно хорошо прокомментирован, но на одном моменте все же остановлюсь. Это касается формулы для расчета температуры — в данном случае дробное значение отбрасывается.

В данном случае, используется датчик U2 (верхний левый угол схемы).

Вот что вышло:

Рис. 1

Следующий шаг — подключение нескольких устройств 1-Wire (в нашем случае DS18B20), к микроконтроллеру. Для подключения использованы различные пины МК. В принципе, можно было сделать несколько различных процедур для считывания температуры (по одной на каждый датчик), но это было бы очень громоздко и неэффективно. По этому, задача была решена таким способом:

 program _1_Wire_4_dev_to_1_line;  var LCD_RS : sbit at PORTC0_bit;                   //Подключаем LCD var LCD_EN : sbit at PORTC1_bit; var LCD_D4 : sbit at PORTC2_bit; var LCD_D5 : sbit at PORTC3_bit; var LCD_D6 : sbit at PORTC4_bit; var LCD_D7 : sbit at PORTC5_bit;  var LCD_RS_Direction : sbit at DDC0_bit; var LCD_EN_Direction : sbit at DDC1_bit; var LCD_D4_Direction : sbit at DDC2_bit; var LCD_D5_Direction : sbit at DDC3_bit; var LCD_D6_Direction : sbit at DDC4_bit; var LCD_D7_Direction : sbit at DDC5_bit;  var Rom: array [0..3] of array [0..7] of byte;     n: byte;     t: string [6];  procedure SaveRom(d: byte); var i: byte; begin      ow_reset(PORTB, 4);                            //Сброс      ow_write(PORTB, 4, $33);                       //Отправка команды "Read ROM"      for i := 0 to 7 do                             //Чтение ROM в массив (Rom).          Rom[d][i] := ow_read(PORTB, 4); end;  function ReadTemp(d: byte): string [6];            //Функция чтения температуры практически не меняется, var i: byte;                                       //отличия лишь в том, что добавляются вставки кода,     a: array [0..1] of byte;                       //необходимые для адресации к конткретному датчику.     b: integer; begin      ow_reset(PORTB, 4);      ow_write(PORTB, 4, $55);      for i := 0 to 7 do                            //После команды "совпадение ROM"          ow_write(PORTB, 4, Rom[d][i]);            //Посылаем ROM код на линию      ow_write(PORTB, 4, $44);                      //А следом команду конвертирования      delay_ms(750);      ow_reset(PORTB, 4);      ow_write(PORTB, 4, $55);                      //Далее, после сброса линии, можно считать      for i := 0 to 7 do                            //температуру,предварительно отправив ROM код.         ow_write(PORTB, 4, Rom[d][i]);      ow_write(PORTB, 4, $BE);      delay_ms(120);      a[0] := ow_read(PORTB, 4);      a[1] := ow_read(PORTB, 4);      b := ((a[1] shl 8) + a[0]) shr 4;      if b > 1000 then b := - (4096 - b);      IntToStr(b, result); end;  procedure LcdDisp; begin      lcd_out(1, 1, 'Temp:');                       //Выводим информацию на LCD      t := ReadTemp(1);      lcd_out(1, 10, t);      t := ReadTemp(0);      lcd_out(1, 6, t);      t := ReadTemp(3);      lcd_out(2, 10, t);      t := ReadTemp(2);      lcd_out(2, 6, t); end;  begin      DDD0_bit := 0;                                //Конфигурация портов на вход, и      DDD1_bit := 0;                                //подключение в встроенным подтягивающим      PORTD0_bit := 1;                              //резисторам .      PORTD0_bit := 1;      lcd_init;      lcd_cmd(_LCD_CLEAR);      lcd_cmd(_LCD_CURSOR_OFF);      While TRUE do begin            if Button(PIND, 0, 100, 0) then begin               if n <= 3 then begin                  SaveRom(n);                  inc(n);               end else                   LcdDisp;               while PIND0_bit = 0 do                     nop;            end;                  end; end.

А вот скрин Proteus’a:

Как видите, различия в коде невелики. В основном, они относятся к введению переменной, для выбора пина порта, на который «подвешен» датчик.

Следующий пример будет сложнее, так как мы будем считывать температуру с 4-х датчиков, подключенных на 1 пин. Сложность состоит в том, что использование одного провода требует определения ROM кодов всех устройств, находящихся на линии, для правильной адресации команд. Но вся беда в том, что встроенная библиотека не позволяет провести поиск всех устройств (об этом будет рассказано в следующем уроке). Приходится узнавать ROM коды устройств по очереди: подключил одно — считал, подключил второе — считал и т.д. Так сейчас и поступим. 

Предлагаю вашему вниманию следующий код:

 program _1_Wire_4_dev_to_1_line;  var LCD_RS : sbit at PORTC0_bit;                   //Подключаем LCD var LCD_EN : sbit at PORTC1_bit; var LCD_D4 : sbit at PORTC2_bit; var LCD_D5 : sbit at PORTC3_bit; var LCD_D6 : sbit at PORTC4_bit; var LCD_D7 : sbit at PORTC5_bit;  var LCD_RS_Direction : sbit at DDC0_bit; var LCD_EN_Direction : sbit at DDC1_bit; var LCD_D4_Direction : sbit at DDC2_bit; var LCD_D5_Direction : sbit at DDC3_bit; var LCD_D6_Direction : sbit at DDC4_bit; var LCD_D7_Direction : sbit at DDC5_bit;  var Rom: array [0..3] of array [0..7] of byte;     n: byte;     t: string [6];  procedure SaveRom(d: byte); var i: byte; begin      ow_reset(PORTB, 4);                            //Сброс      ow_write(PORTB, 4, $33);                       //Отправка команды "Read ROM"      for i := 0 to 7 do                             //Чтение ROM в массив (Rom).          Rom[d][i] := ow_read(PORTB, 4); end;  function ReadTemp(d: byte): string [6];            //Функция чтения температуры практически не меняется, var i: byte;                                       //отличия лишь в том, что добавляются вставки кода,     a: array [0..1] of byte;                       //необходимые для адресации к конткретному датчику.     b: integer; begin      ow_reset(PORTB, 4);      ow_write(PORTB, 4, $55);      for i := 0 to 7 do                            //После команды "совпадение ROM"          ow_write(PORTB, 4, Rom[d][i]);            //Посылаем ROM код на линию      ow_write(PORTB, 4, $44);                      //А следом команду конвертирования      delay_ms(750);      ow_reset(PORTB, 4);      ow_write(PORTB, 4, $55);                      //Далее, после сброса линии, можно считать      for i := 0 to 7 do                            //температуру,предварительно отправив ROM код.         ow_write(PORTB, 4, Rom[d][i]);      ow_write(PORTB, 4, $BE);      delay_ms(120);      a[0] := ow_read(PORTB, 4);      a[1] := ow_read(PORTB, 4);      b := ((a[1] shl 8) + a[0]) shr 4;      if b > 1000 then b := - (4096 - b);      IntToStr(b, result); end;  procedure LcdDisp; begin      lcd_out(1, 1, 'Temp:');                       //Выводим информацию на LCD      t := ReadTemp(1);      lcd_out(1, 10, t);      t := ReadTemp(0);      lcd_out(1, 6, t);      t := ReadTemp(3);      lcd_out(2, 10, t);      t := ReadTemp(2);      lcd_out(2, 6, t); end;  begin      DDD0_bit := 0;                                //Конфигурация портов на вход, и      DDD1_bit := 0;                                //подключение в встроенным подтягивающим      PORTD0_bit := 1;                              //резисторам .      PORTD0_bit := 1;      lcd_init;      lcd_cmd(_LCD_CLEAR);      lcd_cmd(_LCD_CURSOR_OFF);      While TRUE do begin            if Button(PIND, 0, 100, 0) then begin               if n <= 3 then begin                  SaveRom(n);                  inc(n);               end else                   LcdDisp;               while PIND0_bit = 0 do                     nop;            end;                  end; end.

Как видите, код не намного сложнее предыдущего варианта. Последовательность работы следующая: нужно по очереди подключать датчики и нажимать на кнопку. После того, как будет «сохранен» последний (4-й) датчик нажатие на кнопку запустит измерение температуры. Вот результат:

Ну и на последок, еще один примерчик — запись и чтение в регистры DS18B20. Если кто-то не знает, то регистров в этом датчике всего 3 (доступных для чтения/записи): TH, TL, регистр конфигурации. Нас сейчас интересует именно последний. В регистре конфигурации можно изменить всего 2 бита — они отвечают за разрешение датчика и как следствие, за длительность конвертирования. С помощью периведенного ниже кода можно настроить датчик на 9 бит (наименьшая точность и наивысшая скорость — 91 мс).

 program _1_Wire_reg_write;  var LCD_RS : sbit at PORTC0_bit;                  //Инициализация LCD var LCD_EN : sbit at PORTC1_bit; var LCD_D4 : sbit at PORTC2_bit; var LCD_D5 : sbit at PORTC3_bit; var LCD_D6 : sbit at PORTC4_bit; var LCD_D7 : sbit at PORTC5_bit;  var LCD_RS_Direction : sbit at DDC0_bit; var LCD_EN_Direction : sbit at DDC1_bit; var LCD_D4_Direction : sbit at DDC2_bit; var LCD_D5_Direction : sbit at DDC3_bit; var LCD_D6_Direction : sbit at DDC4_bit; var LCD_D7_Direction : sbit at DDC5_bit;  var s: string[6];  procedure WriteConf(TH, TL, cfg: byte); var i: byte; begin      ow_reset(PORTB, 5);                           //Сброс      ow_write(PORTB, 5, $CC);                      //Обращение ко всем датчикам на линии      ow_write(PORTB, 5, $4E);                      //Команда записи ROM      ow_write(PORTB, 5, TH);                       //Отправляем TH      ow_write(PORTB, 5, TL);                       //Отправляем TL и байт регистра конф.      ow_write(PORTB, 5, cfg); end;  function ReadConf: byte;                           //Процедура чтения конфигурации var i: byte;     d: byte; begin      ow_reset(PORTB, 5);      ow_write(PORTB, 5, $CC);      ow_write(PORTB, 5, $BE);                      //Чтение ROM      for i := 0 to 4 do                            //Запоминаем только 4-й принятый байт - регистр конфигурации          d := ow_read(PORTB, 5);      ow_reset(PORTB, 5);                           //Сбрасываем и прекращаем обмен данными      result := d; end;  begin      WriteConf(0, 0, 31);                          //Обращение к процедуре записи      lcd_init;      lcd_cmd(_LCD_CLEAR);      lcd_cmd(_LCD_CURSOR_OFF);      IntToStr(ReadConf, s);      lcd_out(1, 1, 'config' + s); end.

Вот такой маленький, но полезный код. Ниже приведены скриншоты окна Proteus’a. Первый — без записи в регистр, а второй с записью разрешения 9 бит.

Вот и все. Урок подошел к концу, спасибо всем тем, кто его прочитал/просмотрел! Следующий урок тоже буде на тему 1_Wire, но будет рассмотрена другая библиотека, я сейчас занимаюсь ее портированием / дописыванием. В ней (скорее всего) будут «из коробки» доступны такие функции как: поиск датчиков, определение количества датчиков, подключен датчик или нет и т.д. В любом случае, она будет более удобна чем эта.