Поиск устройств на шине 1-Wire (на ассемблере для микроконтроллеров AVR фирмы Atmel)

Случилось тут мне собирать одно устройство неопределенного назначения, в котором используются датчики температуры DS1820. Поскольку датчиков было больше одного, то пришлось думать: как к ним обращаться. Можно было, конечно, заранее вбить в программу коды ROM каждого датчика, но это ведь не наш метод. Хотелось универсальности, чтобы раз и навсегда.  Пришлось обратить внимание на процедуру поиска устройств, т.е. проштудировать Application Note 187. 1-Wire Search Algorithm фирмы Dallas (Maxim). В аппноуте расписан принцип поиска и приведен алгоритм с примером на языке С. Поскольку мне необходимо было реализовать свою идею на ассемблере, да и люблю я его больше, пошел я поиском по сайтам и ничего толком не нашел. Все ссылаются на аппноут, а примера на ассемблере нет. В результате, я перевел Application Note 187. 1-Wire Search Algorithm на русский язык и портировал функцию поиска с языка С на AVR-ассемблер, и предлагаю ее вам, дабы заполнить этот пробел в Интернете.

Начну с теории. Если вникать в принципы поиска не хочется — милости прошу в конец статьи, где находятся ссылка на скачивание готовых модулей, их описание и пример использования.

Теоретическая часть

Везде в Интернете в описаниях шины 1-Wire и поддерживающих ее устройств написано, что процедура поиска устройств SEARCH_ROM (0xF0) сложна и непонятна. На самом деле, не так уж она и сложна, если понять принцип. А принцип я сейчас попытаюсь доступно изложить.

Подробно устройство шины 1-Wire я описывать не буду. Желающих узнать подробности могу отослать к хорошей статье, поясняющей ее работу как на низком, так и на высоком уровне: http://radiokot.ru/articles/13/

Итак, имеем шину 1-Wire, на которой висит одно ведущее устройство (обычно — микроконтроллер) и неизвестное количество ведомых устройств (не обязательно одинаковых). Согласно схеме включения, шина подтягивается к плюсу питания резистором, так что на ней при простое всегда присутствует логическая «1».

Соответственно то, что получает микроконтроллер из шины, является логическим «И» ответов всех устройств. Т.е. если все устройства отвечают в линию логической «1», то 1 AND 1 AND 1 и т.д. дает в результате ту же «1». Если же одно или несколько устройств отвечают «0», то вся шина просаживается в «0» даже если другие устройства ответили «1». Собственно, с точки зрения шины, для передачи «1» устройству вообще ничего не нужно делать, т.к. она итак подтянута к «1» резистором.

Каждое устройство на шине 1-Wire имеет уникальный 64-битный код ПЗУ, который и является тем уникальным идентификатором, который позволяет различать устройства при поиске. Кроме того, весь обмен с определенным устройством возможен только после того, как указан именно его код. Поэтому и поиск устройств на шине сводится именно к получению всех кодов ПЗУ устройств.

Весь обмен с устройствами начинается с того, что ведущее устройство (в большинстве случаев — микроконтроллер) отсылает в шину импульс сброса RESET. Ведомые устройства, получив импульс сброса, отвечают в шину импульсом присутствия PRESENCE. Поскольку все устройства висят на одной шине, они все отвечают в шину одновременно. Но на данном этапе их ответ (PRESENCE) совпадает.

После того, как PRESENCE получен, ведущее устройство начинает поиск, отправляя в шину либо команду 0xF0 (SEARCH ROM) либо 0xEC (ALARM SEARCH). Команду 0xEC мы рассматривать не будем, т.к. она является частным случаем команды SEARCH ROM и предназначена для поиска устройств в состоянии ALARM. Подробнее об этом можно почитать в даташитах к устройствам.

Получив команду SEARCH ROM, каждое устройство передает первый бит своего кода ПЗУ и дополнение этого бита (грубо говоря — его инверсию). Приняв от устройства первый бит и его дополнение, мы должны в подтверждение отправить этот первый бит назад, в шину. Но! Все устройства делают это одновременно, так что контроллер получает логическое «И» первых битов кодов и логическое «И» их дополнений. Что именно передавать в шину в качестве подтверждения? А вот что: на основании бита и его дополнения производится анализ:

  • Если бит = дополнение = «1», это значит, что на линии нет готовых устройств, т.е. никто не ответил. В этом случае поиск прекращается.
  • Если биты не равны, т.е. бит = 0, а дополнение = 1 или наоборот — это значит, что у всех устройств этот бит совпадает. Этот случай простой — мы просто записываем к себе этот бит и переходим к следующим операциям
  • Если бит = дополнение = «0» — это значит, что у устройств первый бит кода ПЗУ не совпадает, т.е. у кого-то «1» а у кого-то «0», это сложный случай, в аппноуте он называется «Discrepancy», что можно перевести как «Различие»

Первый случай, когда бит/дополнение = «1» рассматривать нет смысла — активных устройств больше нет, поиск прекращаем.

Когда бит/дополнение не равны — тут тоже все просто, этот бит одинаков у всех устройств, так что просто запоминаем его в качестве первого бита кода ПЗУ.

Если же есть различие, т.е. бит/дополнение равны «0», тогда и начинается самое интересное. Смысл поиска в том, что приняв от устройства бит, мы должны в подтверждение отправить его назад, в шину. Поскольку в данном случае текущий бит у устройств не совпадает, мы отправляем в шину «0». Это дает нам вот что: все устройства, у которых этот первый бит был равен «1» отключаются от шины и больше не реагируют ни на что до следующего сброса! Т.е. не участвуют больше в поиске. Остаются те, у которых первый бит был равен «0». Они продолжают участвовать в поиске и готовы выслать нам второй бит своего кода, а мы записываем «0» в качестве первого бита кода ПЗУ.

Позицию, где случилось последнее различие, мы запоминаем. Она понадобится нам при следующем поиске, когда мы дойдем до этого места. Т.е. при следующем поиске в этом месте мы увидим, что направление «0» мы уже выбирали и вместо нуля пойдем другим путем: выдадим на шину «1», тем самым, отметая предыдущие устройства, которые мы уже сканировали. Направление меняется именно на последнем различии, в предыдущие различия выбираем тот же направление что и раньше. Это нужно, для того,  чтобы не упустить ни одного устройства.

Итак, весь поиск сводится к последовательности:

  • 1) Принять бит кода ПЗУ и его дополнение
  • 2) На основании этих битов и последнего различия предыдущего поиска принять решение и выдать в шину направление поиска
  • 3) Запомнить полученный бит или выбранное направление в коде ПЗУ
  • 4) Если было различие — запомнить его позицию
  • 5) Перейти к приему следующего бита

Повторяется эта последовательность для каждого из 64 бит. В результате мы получим код ПЗУ одного из устройств. Затем, начинаем поиск еще раз, выбрав другое направление, в случае если встретилось последнее различие. Получим еще один номер ПЗУ. И так до тех пор, пока на шине не останется неизвестных нам устройств.

Чтобы было понятно привожу переведенную блок-схему алгоритма:
Блок-схема алгоритма

Вот в общем виде и весь принцип поиска. Здесь описан именно принцип, а не детальный план. Главное знать, как это делается. Тем, кто решит написать программу поиска самостоятельно я рекомендую изучить Application Note 187. 1-Wire Search Algorithm в первоисточнике. Если с английским туго — могу предложить свой вольный перевод этого аппноута. А теперь, перейдем от теории к практике.

Практическая часть

 

Модуль, содержащий процедуру поиска можно скачать по этой ссылке: Скачать

 

Архив содержит следующие модули:

1-Wire.asm — Замечательный модуль программной реализации 1-Wire. Автор — Radoslaw Kwiecien, http://avr-mcu.dxp.pl

wait.asm — модуль временных задержек. Необходим в модуле 1-Wire.asm. Автор тот же.

1-Wire Search.asm — модуль, содержащий непосредственно процедуру поиска устройств на шине 1-Wire на AVR-ассемблере. Автор — ваш покорный слуга.

Зачем столько? Во-первых не могу не поделиться очень удобными модулями реализации протокола 1-Wire от Radoslaw Kwiecien. Я их перевел, там все понятно. Если кто-то не желает их использовать — милости прошу. Но тогда прочитайте внимательно вступление в модуле 1-Wire Search.asm — там написано, какие внешние подпрограммы используются и обмен между ними.

Если же вы решите воспользоваться моими библиотеками целиком — то достаточно подключить к вашему проекту только модуль 1-Wire.asm. Остальные  подключаются внутри него.

Ну, и напоследок — пример использования поиска:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
; ...

#define F_CPU (4000000) ; Определяем константу с тактовой частотой кристалла - она необходима в модуле 1-Wire.asm

; ...

.include "1-wire.asm" ; Подключаем модули 1-Wire

; ...

rcall OWClearROM_NO ; Обнуляем буфер кода ПЗУ
rcall OWFirst ; Ищем первое устройство на шине

sbrs search_flags, search_result ; Если ошибка - то поиск прерываем
rjmp  EndOfExample

loop1:
; ... Здесь делаем с найденным кодом ПЗУ все что нам угодно ...

rcall OWNext ; ищем следующее устройство на шине. БУФЕР ОЧИЩАТЬ НЕЛЬЗЯ!
sbrs search_flags, search_result ; Если ошибка - то поиск прерываем
rjmp EndOfExample

rjmp loop1 ; и так до тех пор, пока все коды ПЗУ не вычислим.

EndOfExample:

; ... идем дальше ...

Вот и весь пример. Засим  - прощаюсь. Будут вопросы — милости просим. Отвечу.

Поделиться с друзьями:
  • Добавить ВКонтакте заметку об этой странице
  • Одноклассники
  • Facebook
  • В закладки Google
  • Мой Мир
  • Twitter
  • LiveJournal
  • Яндекс.Закладки
  • LinkedIn
  • Reddit
  • StumbleUpon
  • БобрДобр
  • Memori.ru
  • МоёМесто.ru

Comments

  1. >>>Все устройства делают это одновременно, так что контроллер получает логическое «И»...

    Я бы сказал не И, а ИЛИ. Т.к. 0 на выходе хотя бы одного устройства даёт неизменяемый 0 на шине, что эквивалентно оцерации ИЛИ — «хотя бы один».

    А вообще спасибо! Читал статью т.к. незнал алгоритма поиска. Всё прояснилось:)

    1. Нет, в данном случае все правильно, т.к. операция «ИЛИ» применима не для нулей, а для единиц.

      Вот описание логического «ИЛИ»: результат равен 0, если все операнды равны 0; во всех остальных случаях результат равен 1.

      Вот описание логического «И»: результат равен 1, если все операнды равны 1; во всех остальных случаях результат равен 0.

      Покажу на примере:

      Имеется 3 устройства на шине. первое выдает «1», второе «0», третье «1»:

      Вот ваше «ИЛИ»: 1 OR 0 OR 1 = 1

      Вот мое «И»: 1 AND 0 AND 1 = 0

      Так что, контроллер получит именно логическое «И» всех сигналов.

    2. А зачем нужен поиск всех датчиков не зная в каком месте они установлены? Не лучше просто перед установкой в конкретном месте прочитать его id и присвоить ему порядковый номер? Вот тогда мы будем знать в каком месте установлен датчик.

      1. Ага-ага. Можно. А потом, когда этот датчик сдохнет вдруг, мы поменяем датчик, заново прочитаем его ID, полезем в исходники прошивки, поменяем его там, скомпилим прошивку, расковыряем наше устройство, перепрошьем его, запихнем назад, удовлетворенно вытрем пот и стрясем с заказчика 500 баксов за грандиозный ремонт. Хотя можно было просто поменять датчик и устройство нашло бы его само. Нужно, чтобы человек любил работу, а не работа его. Мыслите масштабно! Кроме того, универсальные алгоритмы упрощают жизнь.

    1. И рад бы, да не могу... Пол-года назад у компа накрылся жесткий диск, выцарапывать с него инфу пришлось быстро и без разбору... Видимо перевод аппноута был утерян, потому что среди спасенного найти его не могу ... Сожалею...

  2. Давно это было, не помню точно в чем рисовал, но либо это был Corel Draw, либо Microsoft Visio, ибо других векторных редакторов не юзал. Скорее всего, это был Visio

  3. Программа для рисования блок схем WIZFLOW , весит много меньше чем MV, функциональности для рисования блок схем хватает.

    А статья отличная много стало сразу понятно.

  4. Большое спасибо!!! Очень познавательно. Программы правда пишу на Алгоритм-Билдере, и портировал выложенный Вами код на него (если интересно дайте адрес поделюсь). Вопрос вот в чем: а у Вас случайно нету процедуры расчета контр.суммы побитно (чтоб не использовать табличный метод), уж больно он прожорлив :- (, при том что приём все равно идет побитно и скорости контроллера хватает чтоб вычеслять ее на лету. Когда то где то видел, но сейчас почему то не могу найти???

  5. Вот, нашел тут у себя:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    .def    wiretemp = r22      ; регистр под нужды 1-wire                          
    .def    wireres  = r2       ; обязательно из числа r0...r15  

    ;*********************************************************************     
    crc8:
    ; выполняет подсчет CRC по алгоритму 1-Wire
    ;   вход: r0 - считанный байт
    ;   выход: CRC - содержит подсчитанную сумму
    ;   портит: регистр Z
    ;   примечание: перед первым вызовом CRC необходимо обнулить

        push    r0
        ldi wiretemp, 8
        mov wireres,wiretemp
    r1w_loop_crc:
        lds wiretemp, CRC
        eor r0, wiretemp
        ror r0
        mov r0, wiretemp
        brcc    zero
        ldi wiretemp, 0x18
        eor r0, wiretemp
    zero:
        ror r0
        sts CRC, r0
        pop r0
        ; 4 следующие команды делают циклический сдвиг r0
        push    r0
        ror r0
        pop r0
        ror r0
        ; сдвиг закончен - очень удобно, не правда ли?!
        push    r0
        dec wireres
        brne    r1w_loop_crc
        pop r0
        lds r0, CRC

        ret

    ...
        .dseg
    CRC:    .byte   1       ; расчитываемая контр.сумма

    1. Я же уже писал в комментариях: «Пол-года назад у компа накрылся жесткий диск, выцарапывать с него инфу пришлось быстро и без разбору... Видимо перевод аппноута был утерян, потому что среди спасенного найти его не могу ... Сожалею...»

  6. Спасибо за статью =) почитал её, почитал ссылки внутри, и теперь 1-wire понимаю практически полностью =)

    Статья очень полезна для желающего работать с технологией 1-wire и знающего о ней на уровне «ну это когда они все как-то на одном проводе 0_о» =)

  7. Спасибо за статью. Как обычно собираю мозаику из разных источников. Где то понял одну часть, где то другую. Эта статья меня можно сказать ввела в курс дела в алгоритме поиска. (работать с одним устройством на шине умел давно). Но есть здесь и слабое место в описании приема битов и дополнений. Не хватает таблицы битов и дополнений, не понятно было слово «дополнение». И по поводу блок схемы алгоритма. С помощью нее я наконец полностью разобрался в теме. А потом уже с пониманием нашел в

    Application note — AVR318: Dallas 1-Wire master таблицу алгоритм поиска и понял что она проще. Она на стр 13. Её тоже можно добавить в статью.

    1. Ну вроде все нормально, понятно написано: «Получив команду SEARCH ROM, каждое устройство передает первый бит своего кода ПЗУ и дополнение этого бита (грубо говоря — его инверсию).»

      Слово «инверсия» здесь ключевое ...

      За аппноут — спасибо, гляну на досуге.

  8. да понятно написано. для своего нового проекта сразу изучаю жпрс модем, лсд дисплей, и поиск по 1варю. на русском и английском. где то уже несображаю от перегрузки. а еще в догонку буду ваять сайт на гугле для мониторинга. тоже разбираться надо.

  9. Пара вопросов:

    >каждое устройство передает первый бит своего кода ПЗУ

    Точнее говоря, младший бит (LSB, bit 0)? Например, для DS18B20 это получится 0×28 => 00101000 => бит 0.

    >Остаются те, у которых первый бит был равен «0». Они продолжают участвовать в поиске и готовы выслать нам второй бит своего кода, а мы записываем «0» в качестве первого бита кода ПЗУ.

    Т.е. в результате получим список сер. номеров упорядоченный сначала по одинаковым младшим битам, затем по длине последовательностей нулей.

    т.е примерно так (несколько db18b20):

    ...10000 00101000

    ...1000 00101000

    ...100 00101000

    ...100 00101000

    ...10 00101000

  10. Здравствуйте, не подскажите куда копать...

    Сделал на асме реализацию обмена по 1-wire. считываю серийник с iButton и термодатчика без проблем командой 33h. Но другие команды не распознает. в частности 0CCh и 0BEh. просто не отвечает после них. В чем может быть дело

    1. Сложно сказать. Попробуйте воспользоваться не своей реализацией обмена, а какой-нить другой. Если ошибка исчезнет, значит проблема в вашем коде

      1. Спасибо разобрался. Скачал данную реализацию — а она почти один в один. Просто криво Rom — команды слал)))

  11. Подскажите пожалуйста

    в строке

    ; ... Здесь делаем с найденным кодом ПЗУ все что нам угодно ...

    необходимо эти действия или какие-то не нужны?

    1) 0хСС послать устройству

    2) 0×44 послать устройству

    задержка 740 мс

    3) 0хСС послать устройству

    4) 0хВЕ послать устройству

    5)считать с устройства

    6) преобразовать

    7)вывести на экран

    И как вывести на экран одновременно 2 и больше показаний а не в одной строчке

        1. Да нету проекта. Делал контроллер. Предназначался он для контроля аквариума с экзотическими рыбами. С двумя датчиками температуры. В процессе и родилась мысль исследовать поиск. Обвесил всеми 1-wire присосками какие нашел. Датчики, домофонные i-button и все такое. Потом когда статью закончил — все разобрал и доделал аквариум 🙂 Так что проект, как таковой, отсутствует.

  12. Ладненько спрошу по-другому)

    После метки loop1: в ROM_NO будет вычисленный RoM сначала 1 устройства, потом 2, потом n. Правильно?

    1. Вставил после loop1 код. Гляньте плиз по метке DS нужно какие-то команды удалить или добавить что-то?

      rcall OWClearROM_NO

      rcall OWFirst

      sbrs search_flags, search_result

      rjmp EndOfExample

      loop1:

      ; Код

      main:

      rcall ds

      rcall bcd_convert ;преобразование

      ldi lcd_command,$01

      rcall lcdcmd

      ldi wreg, 3

      rcall ds_longdelay

      rcall display; вывод на экран

      rcall OWNext

      sbrs search_flags, search_result

      rjmp EndOfExample

      rjmp loop1

      EndOfExample:

      rjmp main

      Ds:

      rcall ds_reset

      ldi ds_command, 0xCC

      rcall ds_write

      ldi ds_command, 0×44

      rcall ds_write

      ldi wreg, 255

      rcall ds_longdelay ; 178ms

      ldi wreg, 255

      rcall ds_longdelay ; 178ms

      ldi wreg, 255

      rcall ds_longdelay ; 178ms

      ldi wreg, 255

      rcall ds_longdelay ; 178ms

      ldi wreg, 60

      rcall ds_longdelay ; 40ms

      rcall ds_reset

      ldi ds_command, 0xCC

      rcall ds_write

      ldi ds_command, 0xBE

      rcall ds_write

      ldi wreg, 5

      rcall ds_delay

      rcall ds_read

      ret

      ds_reset:

      cbi PORTB, PB0

      ldi wreg, 0xFF

      out DDRB, wreg

      ldi wreg, 220

      ldi wreg, 0xFE

      out DDRB, wreg

      ldi wreg, 220

      rcall ds_delay

      ret

  13. недавно асмой увлекся...

    а на СИ имел дело с этой шиной... только криво получалось

    за статейку и файлы спс, пойду переводить Application Note 187. 1-Wire Search Algorithm

  14. Отличная стать! Хорошо написана, вот только для меня есть один скользкий момент исходя из блоксхема алгоритма содержит блок где направление поиска берется равным биту из пзу в текущей позиции как это может быть веди мы ищем этот бит? Или может там просто надо написать текущий бит? Прошу если можно подробнее прокомментировать этот момент . Заранее спасибо!

  15. Возникла проблема с подсчетом CRC для DS18B20. Использую известный алгоритм CRC8 (с полиномом 31h). Датчик выдает совершенно другое значение (при чтении памяти и кодов). По какому алгоритму он работает ?

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

Ваш e-mail не будет опубликован.