Это уже наш 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, но будет рассмотрена другая библиотека, я сейчас занимаюсь ее портированием / дописыванием. В ней (скорее всего) будут «из коробки» доступны такие функции как: поиск датчиков, определение количества датчиков, подключен датчик или нет и т.д. В любом случае, она будет более удобна чем эта.
Прикрепленные файлы:
- 1_wire(mikroPascal part 5).rar (851 Кб)