1-wire slave из подручных материалов.

Иногда бывает необходимость создать какое-либо устройство для подключения к какой-либо экзотической сети сбора данных. Оговорюсь сразу, предлагается к рассмотрению отнюдь не готовое устройство, статья скорее предназначена разработчику и посвящена работе в сети обмена данными, не имеющей готовой аппаратной поддержки, с использованием имеющихся аппаратных ресурсов с целью максимально разгрузить процессор.

 Так вот однажды, в незапамятные времена, понадобилось подключить доплеровский расходомер к сети датчиков 1-Wire. Для тех, кто не знает, что это за баунти, поясню подробно.

Это протокол передачи данных по двум проводам (третий – питание, но он не обязателен: многие устройства поддерживают так называемое «паразитное» питание). Протокол достаточно непритязателен к качеству физической линии. Автор всех уровней протокола – фирма Dallas Semiconductor, и ее дочернее предприятие iButton(я еще помню те времена, когда они жили на одном сайте, и никакого Maxim Semi там не было. Хотя должен признать, поглощение получилось взаимовыгодное). Теперь это www.maxim-ic.com . Для подключения по этому протоколу фирмой разработано немало устройств таких как АЦП, термометры, зарядные устройства, память, идентификационные устройства, часы реального времени и многие другие. Посмотреть их можно на www.maxim-ic.com/products/1-wire и www.maxim-ic.com/products/ibutton .

Рассмотрим протокол более подробно.

Опросом устройств занимается Мастер сети, или Ведущий. Для его реализации есть куча готовых контроллеров, от простых формирователей уровня для RS-232 и соответствующих рекомендаций по самостоятельному написанию интерфейса, до полнофункциональных I2C и USB контроллеров, которые можно как подключать к компьютеру, так и встраивать в собственные изделия.

А вот для ведомых устройств подобных контроллеров не существует. То есть изделия пресловутой фирмы «максим» мы можем использовать в этой сети, а свои – типа нет. Нехорошо это… Вот и попробуем заполнить этот пробел. Надеюсь, что поскольку при этом пользуюсь открытой документацией, мне за это не светит года три поселения за нарушение патентных прав J

Базовая схема фрагмента сети выглядит следующим образом: (Рис.1)

Рисунок 1.

На стороне Ведущего, в простейшем случае, должен быть приёмник сигнала с линии, источник тока (он может питать 1-W устройства), и передатчик с тремя состояниями. На стороне Ведомого схема несколько проще: должен быть приёмник, аналогичный  тому, что применен в Мастере, и ключ, замыкающий шину данных на землю. Сопротивление ключа должно быть не более 100 Ом. Пока – как видите, всё просто.

Теперь нам нужно рассмотреть временные диаграммы физического уровня протокола.

Обмен начинается с передачей Ведущим импульса сброса (Reset pulse), длительностью tRSTL >= 480 мкс (рисунок 2). В ответ Ведомое устройство передает импульс присутствия (Presence pulse), длительностью tPDL =60..240 мкс. Если на линии несколько устройств, то отвечают, естественно, они все. По наличию импульса присутствия Ведущий определяет, есть ли устройства на линии; для Ведомых – это подтверждение того, что они обнаружили начало информационного кадра.

Рисунок 2. Импульс сброса и импульс присутствия.

За процедурой сброса/присутствия следует информационный обмен. По сути своей, протокол является синхронным. Ведущий передает стробирующий импульс, короткий, длительность его tW1L = 1..15 мкс, и при передаче, и при приеме данных. При передаче Ведущий формирует только строб-импульс, если хочет передать «1», и затягивает его до времени tW0L = 60-120 мкс, если хочет передать «0». Это показано на рисунке 3.

Рисунок 3. Передача Ведущим битов «1» (write-one) и «0» (Write-zero).

После формирования бита «0» Ведущий перезаряжает емкость линии, повышая ток, подтягивающий ее к питанию, на время tREC>=1мкс (если, конечно, схемотехника позволяет).

При приеме данных Ведущий передает только стартовый импульс, а  затем освобождает линию, переводя передатчик в состояние высокого импеданса. Линия подтягивается резистором к питанию. Ведомый, обнаружив падающий фронт, формирует свою информационную часть: если нужно передать ответ «0», то линия ключом подтягивается к земле (Рис.4) на время не менее 18 мкс и не более tSLOT – (tREC >= 1 мкс) ˜ 59 мкс,

Рисунок 4. Прием бита Ведущим (read-data)

Обычно – это время составляет 40-50 мкс для большинства ведомых устройств

Ведущий проверяет состояние линии в окне tMSR ˜ 15..18 мкс., время tREC он так же использует для перезаряда линии.

Ну вот в общем-то и всё самое главное. Более подробно можно почитать на сайте 1-wire, но для построения ведомого устройства этого уже достаточно.

Прежде всего, определимся со схемотехникой. Учитывая, что протокол Ведомого вообще говоря является достаточно ресурсоемким (прерывания каждые 100 мкс; время реакции на прерывание – не более 4 мкс , что выяснено экспериментально; еще через 30-40 мкс нужно снять «0» с линии при передаче бита; да устройство при этом должно успевать и еще что-то делать), то возьмем  процессор AtMega16, и там где можно – используем его аппаратные ресурсы, освободив тем самым процессор от ненужной суеты.

Принципиальная схема изображена на Pис.5. Схема включения процессора типовая, особенностей не имеет. Тактирование – кварцем, на частоту 7.3728 МГц. XP1 – разъем для программатора, XP2 – подключается к шине 1-wire устройств.

Сопряжение с линией сделано на U1 – повторитель с тремя состояниями на выходе. Приемник (U1:A) выбран постоянно. Для передачи используется вход разрешения U1:B. При подаче на него «0» выход переходит из третьего состояния в «0»0 и замыкает линию.

Диодная сборка VD1 и R1 образуют защиту линии, R2 – создает на ней состояние «1», когда устройство отключено от линии.

Рисунок 5. Схема принципиальная интерфейса AtMega16 к 1-Wire

Теперь определимся с ресурсами процессора. Для приема и передачи битов будем использовать имеющийся UART. Для детектирования падающего фронта стробирующего импульса используем прерывание INT0. Итого, нам для работы протокола понадобится три вывода процессора и одна внешняя микросхема. Справедливости ради надо заметить, что упрощенная реализация протокола потребовала в своё время всего лишь одного вывода процессора. Но она получилась весьма ресурсоемкой, написана очень давно, да еще и целиком на ассемблере, поэтому мало годится для «учебной» модели.

Определимся с тем, что должна уметь наша программа.

  1. Формировать и принимать временные диаграммы – импульс сброса, импульс присутствия, передача «0» и «1», прием бита.
  2. Обрабатывать команды протокола 1-Wire.
  3. Обработка дополнительных (пользовательских) команд

 Рассмотрим основные команды транспортного уровня протокола. И начнем мы рассмотрение - со структуры кадра.

Кадр начинается передачей Ведущим импульса сброса. Ведомые отвечают на него импульсом присутствия (рис.2).

Далее Ведущий передает команду «функции ROM» (ROM function command). Код команды состоит из восьми бит, передаются биты – младший идет первым. Основных  команд – 4.

  • Команда 0x33. Чтение ROM (Read ROM).

Эта команда может быть использована, если ведомое устройство на линии – единственное. В ответ Ведомый передает 8 байт собственного идентификационного кода. Первый байт – код 1-Wire устройства, далее 6 байт серийного номера, и один байт циклического контроля. Эта команда, кстати, используется для чтения iButton подобных DS1990 – да, те самые, что используются в замках на подъезде. Справедливости ради надо заметить, что код на iButton оттиснут прямо на крышке, все 8 байт. Так что, с них можно делать пластилиновые слепки, как с обычных ключей ;-).

  • Команда 0xСС -  пропуск ROM (Skip ROM).

Также может использоваться только когда ведомое устройство на линии единственное. Непосредственно за этой командой следует команда обращения к памяти/управления.

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

  • Команда 0x55 – совпадение с ROM (Match ROM).

Команда позволяет обращаться к устройству на шине с конкретным идентификационным кодом, который в данном случае играет роль сетевого адреса. Следом за этой командой передаются 8 байт идентификации. Для того, чтобы Ведомый принял последующую команду, все байты должны совпасть с его идентификационным кодом. Тогда он примет и выполнит передаваемую в этом кадре команду памяти/управления.

  • Команда 0xF0 – поиск ROM (Search ROM).

Эта команда предназначена для поиска устройств. После приема кода команды каждое устройство передает последовательно, начиная с 0-го, биты идентификатора. Сначала передается прямой бит, затем инверсный. Вследствие того, что устройства объединены по монтажному ИЛИ, если бит у двух и более устройств совпадают – он распознается однозначно, если не совпадают – «0» принимается на обоих битах. Для дальнейшего декодирования Ведущий передает необходимое ему для дальнейшей работы значение бита. Ведомый, приняв этот бит, сравнивает его со своим, и при несовпадении прекращает дальнейшую обработку команды. Следом за этой последовательностью можно передавать команду обращения к памяти/управления. Если ведомое устройство примет все биты идентификатора как равные записанным в его ROM, оно выполнит команду.

Собственно алгоритм использования Мастером этой команды для поиска и получения идентификационных кодов для всех устройств, находящихся на шине, весьма запутан и достоин отдельного рассмотрения. Поэтому скромно не будем приводить его в данной статье.

Вслед за последовательностью команды «Функции ROM» передается команда обращения к памяти/управления. Это команды специфичны для каждого устройства. У них различная длина, различное назначение байт, поэтому подробно останавливаться на них не имеет смысла. Рассмотрим те, что обрабатывается в примере программы. Напоминаю, они приведены только для примера.

  • Команда 0xBE. Чтение страницы памяти (Read Scratchpad).

Эта команда чтения страницы памяти устройства. Если страница читается целиком (для DS1820, например, это семь байт), то Ведомый в конце автоматически формирует байт циклического контроля.

  • Команда 0x4e. Запись страницы памяти (Write Scatchpad)

Команда записывает в страницу памяти по фиксированному адресу два байта. Поскольку циклическим контролем они не защищены, результат операции надо проверять чтением.

В протоколе 1-Wire выполнение любой команды может быть прервано передачей Ведущим импульса сброса.

Теперь – краткие пояснения по тексту программы.

Программа написана на языке Си. Компилятор - IAR C/EC++ Compiler for AVR 3.10C. Он обеспечивает уровень оптимизации, вполне достаточный для написания обработчиков прерываний, даже критичных по времени реакции. Конечно, ассемблер работает куда быстрее, и многие утверждают, что на нем и надо бы писать, но… алгоритмы оптимизации совершенствуются, быстродействие процессоров растет, а цена их от этого не увеличивается особо. Так имеет ли смысл поступаться переносимостью программы и писать на ассемблере то, что не является жизненно необходимым? Да и скорость разработки в наше кризисное время - немаловажный фактор. Этот алгоритм с минимальными переделками был перенесен на ADSP-2181 и ADSP-2191. Изменения оказались обусловленными только разницей в периферии, что, впрочем, совершенно естественно. Но сейчас не об этом.

Постарался основные моменты прокомментировать в тексте программы.

  • Файл Main.c
void startup_init(void).Здесь производится вся настройка оборудования – направления портов ввода-вывода, режимы таймеров, начальные настройки последовательного порта. Прерывания можно и разрешить.#pragma type_attribute=__C_taskvoid main(void)

Собственно, вход в программу. Вся работа по формированию протокола 1-Wire производится в прерываниях, так что в main остаются только вызовы функций инициализации да «вечный» цикл for()

  • Файл 1-W_hdr.c
Здесь как раз всё самое интересное.void _1w_init(void)

Эта функция производит начальную настройку переменных протокола. Все переменные помещены в структуру _1w.

#pragma vector=INT0_vect__interrupt void _1W_bitstart_interrupt(void)

Это обработчик прерывания, которое вызывается обнаружением фронта стробирующего импульса. Время реакции на это прерывание должно быть не более 4-5 микросекунд. Обработчик, обнаружив падающий фронт, инициирует передачу UARTом байта 0x0, если ему установлен «0» в переменной _1w.to_out_bit, и перезапускает таймер на 400 микросекунд – для обнаружения импульс сброса. Кроме того, если отработало прерывание таймера и в этот момент на линии был «0» - это же прерывание используется для обнаружения восходящего фронта (конца импульса сброса). В этом случае, таймер устанавливается на примерно 50 мкс - это необходимая задержка перед началом формирования импульса присутствия. (вообще-то многие приложения работали и без этой задержки, но уже при написании статьи, при проверке программы натолкнулся  на необходимость ее для DS2482 (мост I²C - 1-Wire).

#pragma vector=TIMER2_COMP_vect__interrupt void _1W_bittimeout_interrupt(void)Прерывание обработчика таймаута на обнаружение импульса сброса. По достижению таймаута это прерывание проверяет состояние входа данных, и если на нем «0» - то контроллер внешних прерываний перенастраивается на восходящий фронт на INT0. Вторая фаза работы этого же таймера начинается по обнаружению конца импульса сброса. После формирования необходимой задержки  перенастраивается UART так, чтобы длительность импульса составляла около 100 микросекунд, и передается байт «0».#pragma vector=UART_RXC_vect__interrupt void _1W_bitend_interrupt(void)

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

В этой функции производится вся обработка  транспортного уровня протокола – сборка байтов, обработка команд ROM, проверка идентификатора, формирование циклического контроля. К сожалению, порой ресурсы процессора приходится всё же экономить, поэтому обработка пользовательских команд размещена тут же. Hа примере команд Read Scratchpad и Write Scratchpad можно построить свой обработчик специализированных команд. Единственное, что следует помнить – обработку следует закончить микросекунд за 30. Такие операции, как запись во flash, следует выносить из этого прерывания. Лучше всего – в цикл где-нибудь в main, как это сделано в примере.

void _1w_to_send_string(char first, char __generic * p, char len)

Это простая функция, устанавливающая указанный буфер данных на передачу. Сначала будет передан байт, указанный в first, затем – последовательно содержимое буфера, в конце будет передан байт циклического контроля. Общая длина переданных данных составит len байт.

 

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

Пару слов о написании собственных команд, пожалуй, необходимо сказать. Протокол достаточно медленный, можно сравнить его скорость с передачей по последовательному порту 9600 бод. Исходя из этого, лучше обмениваться с устройством короткими командами, и чем их меньше в обработчике прерываний – тем лучше. За пример для подражания, имхо, лучше всего взять протокол MODBUS. Для него достаточно двух команд: чтения N байт (или слов, дело вкуса) с адреса M, и запись байта (слова) по адресу. Логичнее всего в прерывании ограничится приемом команды в буфер, а собственно обработку делать во внешнем цикле. При этом следует помнить, что до начала приема следующей команды в этот же буфер пройдет минимум 500 мкс – за это время вполне можно добежать до канадской границы.

Если пользовательская программа использует прерывания, и если не принять специальных мер - могут начаться проблемы с работой канала. Это связано с тем, что при вызове вектора прерывания контроллер прерываний AVR запрещает глобальные прерывания, сбрасывая I-бит. Команда RETI восстанавливает его состояние. Получается, что аппаратно в ядре не предусмотрена вложенность прерываний, поэтому нужно самому при входе в обработчик прерывания разрешить необходимые прерывания 1-wire, и затем установить I-бит. Таким образом, будет достигнута вложенность обработки прерываний программным способом.

Ну вот, пожалуй, и всё… Исходный текст программы прилагается, экспериментируйте. Кинг регардз!

Обсуждение статьи на форуме

   Дмитрий Михайлов.