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

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

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

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

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

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

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

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

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

  • Посылаем команду сброса;
  • Посылаем общую команду;
  • Посылаем функциональную команду;
  • Вот схема (одинакова для всех примеров) и код :

    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-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, но будет рассмотрена другая библиотека, я сейчас занимаюсь ее портированием / дописыванием. В ней (скорее всего) будут «из коробки» доступны такие функции как: поиск датчиков, определение количества датчиков, подключен датчик или нет и т.д. В любом случае, она будет более удобна чем эта.


    Прикрепленные файлы:

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

    Ваш адрес email не будет опубликован.