Эмулятор DS18b20 на ATTiny2313

Мне несколько раз приходилось делать устройства, которые должны были так или иначе реагировать на изменение температуры (а не просто отображать), и пару раз я нарывался на ошибки уже при работе устройства, потому что не достаточно хорошо протестировал работу с температурой при отладке. Поэтому, когда oss попросил меня изобразить волшебную коробочку с кнопками и дисплеем, которая прикидывалась бы градусником, идея пришлась мне по нраву.

Коробочка снабжена индикатором на три разряда, который показывает установленную температуру, двумя кнопками для установки нужного значения и портом 1-wire. Температуру можно выставлять в диапазоне от -55 до +99 градусов. Внутре у ней ATTiny2313.

Так-как задачка полной эмуляции всех фич градусника передо мной не стояла, коробочка обделена многими крутыми штуками. Проще сказать, что она умеет:
— Команду пропуска адреса (Skip ROM)
— Команду чтения адреса (Read ROM)
— … сравнения адреса (Match ROM)
— Чтение памяти (той, где температура)

Иначе говоря, эмулятор покрывает базовый функционал датчика. А вот работать с порогами температуры, командой поиска (Search ROM) и сохранением настроек, он не умеет.

Серийный номер (он-же адрес 1-wire) выставляется при прошивке, и без программатора сменить его нельзя. Это конечно большой минус, но для устройств, в которых всего один термометр, адрес не важен.

Исходники (AVR Studio 4 + WinAVR)
Схема
А под катом, небольшой спич о том, как это работает



Динамическую индикацию и работу с кнопочками оставим в стороне — в них нет ничего интересного. Индикация организована на прерывании от TIMER1, а кнопки опрашиваются в главном цикле программы. Речь пойдет о эмуляции 1-wire слейва.

Самая первая часть механизма — определение RESET импульса. Любой импульс, длиннее 480uS считается импульсом сброса, после него конечный автомат 1-wire должен вернуться в начальное состояние. Для того, чтобы можно было поймать этот импульс в любой момент, я использовал внешнее прерывание (с той ножки, на которой висит пин 1-wire) и таймер, который отсчитывал 480uS. По спаду на линии 1-wire происходит прерывание, которое запускает таймер. По фронту (переходу с 0 на 1) прерывание происходит снова, и отключает таймер. Если импульс оказался достаточно длинным, и фронт не успел, то произойдет прерывание от таймера, в котором и творится все остальное безобразие.

Да-да, вся работа с 1-wire, по-сути, происходит внутри прерывания. Сначала, мы дожидаемся, пока закончится RESET-имульс (т.е. ждем высокого уровня на линии), затем, подождав 30uS, выдаем импульс, длиной в 100uS — это PRESENCE сигнал, который сообщает, что на линии кто-то есть. Выдав PRESENCE, ждем команду от мастер-устройства…

Надо заметить, что все эти «ждем» организованы как простой цикл while
while ((OW_READ & (1<<OW_PIN))!=0); //Вот так мы ожидаем низкого уровня на D2 (OW_PIN). А OW_READ - это PIND

И если бы мастер вдруг передумал вести передачу дальше, это привело бы к зависанию устройства. Поэтому пришлось применить сторожевой таймер, который быстро, решительно сбрасывал МК через 500мс, если работа с 1-wire не завершалась корректно. На самом деле, 500мс это сильно много — можно было взять и 100. Значение температуры, которое мы выставили при помощи кнопок, сохраняется после сброса МК (если это конечно не отключение питания).

Кроме того, сразу как только поймался RESET-импульс, я снова разрешаю прерывания, на случай неожиданного сброса или чего-то в этом духе. А вот прерывание от второго таймера, на котором висит работа с индикатором, отключено (иначе оно может сбить тайминги 1-wire), поэтому во время работы 1-wire, индикатор предательски мигает :)

За прием команды отвечает вот этот код:

unsigned char Read_byte()
{
 unsigned char tmp_count;
 unsigned char tmp_value=0;

 wait4high();

 for (tmp_count=0; tmp_count<8; tmp_count++)
 {
  wait4low();
  _delay_us(15);
  
  tmp_value = tmp_value >> 1;
  if ((OW_READ & (1<<OW_PIN))!=0) tmp_value |= 0x80;
  wait4high();
 };

 return tmp_value;
};


Тут все просто: мы ждем спада на линии, и, подождав 15uS, смотрим уровень — если там все-еще 0, значит передается 0. Иначе (если линия поднялась быстрее чем за 15мкс) — единица. Так повторяется 8 раз — для каждого бита. При этом ожидание организовано на простом цикле. На случай неожиданностей у нас есть watchdog.

Приняв первую команду, мы определяем, что необходимо сделать. К примеру, если пришла команда Read ROM, то мы передаем на линию 8 байт — серийный номер устройства. Передача осуществляется примерно так-же, как прием:

void Send_byte(unsigned char _data)
{
 unsigned char tmp_count;

 wait4high();

 for (tmp_count=0; tmp_count<8; tmp_count++)
 {
  wait4low();
  if ((_data & 1)==0) ow_down();
  _data = _data >> 1;
  _delay_us(60);
  ow_up();
  wait4high();
 };
};


Дожидаемся спада на линии (который генерирует мастер) и, если надо передать 0 — прижимаем ее к земле на 60uS. Если надо 1 — просто не трогаем линию. Все просто. Главное — успевать за таймингами. МК тактируется от внутренней RC цепочки на 8MHz, поэтому времени нам хватает с запасом.

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

Если мы приняли команду Match ROM, то принимаем еще 8 байт (серийный номер, передаваемый мастером) и сравниваем с нашим серийником. В случае совпадения — переходим к обработке команд. Иначе — отваливаемся и не шумим на линию до следующего RESET.

Для базовой функциональности хватает всего одной команды — чтения памяти. Ведь на команду замера температуры градусник никак не отвечает, и ее можно не обрабатывать. При получении команды 0xBE мы выдаем на линию 9 байт, первыми двумя идет температура, а дальше примерно то, что должно быть в памяти настоящего DS18B20 + байт CRC.

Работает это вот так. Да, я вас обманул и никакой «коробочки» на самом деле нет :) Может oss когда нибудь соберет это в корпус и тогда будет красивая фотка.

Нет, индикатор на самом деле так не мерцает. Как и монитор ;-) Вина на это полностью возлагается на фотик.

Для демонстрации была заюзана вот эта программка (хоть на что-то она пригодилась!)
  • +14
  • 12 февраля 2013, 01:23
  • dcoder
  • 2
Файлы в топике: draw.PNG, DS18B20_emulator.zip

Комментарии (40)

RSS свернуть / развернуть
Вы всегда хорошие статьи пишите! Приятно почитать.
+3
четырьмя кнопками для установки нужного значения
А на схеме — вдвое меньше.
Алсо, два вопроса
1) Почему бы не работать с 1-Wire через UART? Он бы взял тайминги на себя.
2) Почему бы не запихать этот функционал в уже сделанную коробочку — I2C/1-wire сниффер твоего же авторства?
+1
  • avatar
  • Vga
  • 12 февраля 2013, 02:13
А на схеме — вдвое меньше.
ой :) на самом деле таки 2.
Почему бы не работать с 1-Wire через UART? Он бы взял тайминги на себя.
интересная идея. хотя в данном случае от этого легче бы не стало. Но все равно про использование UART и в слейв-режиме я как-то не думал
Почему бы не запихать этот функционал в уже сделанную коробочку — I2C/1-wire сниффер твоего же авторства?
а зачем? да и эта коробочка после покупки лог анализатора и раскуривания FTDI оказалась не нужна
0
Хорошая работа, скопирую в архив ;-) Когда-нить пригодится :)
0
О Господи!
Еще один исходник с сакраментальной фразой:
TIFR |= 1<<TOV0; //Сброс флага TOV0
Когда будем изучать матчасть, товарищи? :)
+1
А что не так?
0
Операция |= не так. Надо TIFR = 1<<TOV0; //Сброс флага TOV0
0
Ну будет на одну операцию меньше. Сэкономим 1 такт. Оно того не стоит
0
Если до этого возникло какое-нибудь другое прерывание, но мы его ещё не обработали, например, TOV1, то мы и его нечаянно сбросим = пропустим.
0
Да нет. Флаги тут записью «1» сбрасываются, так что все нормально.
0
Если до нашей операции TIFR |= 1<<TOV0; сработал к примеру флаг TOV1, но мы его ещё не обработали, то
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|1<<TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF, то есть сбросу ВСЕХ прерываний в данном регистре, даже ещё не обработанных.
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то для сброса ТОЛЬКО TOV0 правильной будет как раз запись TIFR = 1<<TOV0;
0
Ситуация возможна только если в момент выполнения этого кода запрещены прерывания. Я правильно понимаю?
Если же флаг какого-либо прерывания поднимется в момент выполнения инструкции стоящей до операций чтение-модификация-запись и прерывания не запрещены в этот момент, произойдет обработка возникшего прерывания, а только потом мы дойдем до
TIFR |= 1<<TOV0; //Сброс флага TOV0

Таким образом на практике потенциальную опасность данная конструкция несет, если она выполняется в теле обработчика прерывания. Ну или прерывания запрещены «искуственно».
0
Согласны ли Вы, что TIFR |= 1<<TOV0; несет потенциальную опасность, если она применена не в том месте и не в то время (по невнимательности или незнанию), а TIFR = 1<<TOV0; такой опасностью не обладает? Тем более когда ещё применять такую конструкцию, как не в теле обработчика прерывания или там, где прерывания запрещены.
0
Да я то согласен. И ничего оспорить не пытаюсь.
Просто впервые с этим моментом встретился и хотел понять все нюансы. Спасибо за подсказки.
0
Не в этом дело. Операцию |= нельзя применять для сброса битов в TIFR, т.к. это «Чтение-Модификация-Запись». Применяя |=, Вы можете несанкционированно сбросить биты остальные биты в TIFR: OCF1A, OCF1B, ICF1,OCF0B,TOV0, OCF0A, если они установлены.
Пример: установился ICF1. Когда Вы захотите сбросить свой TOV0, то своей |= Вы ПРОЧИТАЕТЕ ICF1(1), и тут же запишете эту 1 в ICF1. И он сбросится. А Вы про это и знать не будете. Это приведет к глюкам, которые никогда не отловите. Поэтому запомните золотое правило AVR: Сбрасывать TIFR только так: TIFR = (1<<TOV0);
+1
• Bit 1 – TOV0: Timer/Counter0 Overflow Flag
The bit TOV0 is set when an overflow occurs in Timer/Counter0. TOV0 is cleared by hardware
when executing the corresponding interrupt handling vector. Alternatively, TOV0 is cleared by
writing a logic one to the flag. When the SREG I-bit, TOIE0 (Timer/Counter0 Overflow Interrupt
Enable), and TOV0 are set, the Timer/Counter0 Overflow interrupt is executed.
В чем проблема?
0
Если до нашей операции TIFR |= 1<<TOV0; сработал к примеру флаг TOV1, но мы его ещё не обработали, то
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то правильной будет как раз запись TIFR = 1<<TOV0;
0
Что-то глубоко во мне подсказывает что TIFR = 1<<TOV0; это далеко не битовая операция.
0
Дело не в этом. Если в регистре имеются флаги иных прерываний (сравнение и т.д.), они операцией |= также сбросятся, и соответствующие прерывания не возникнут. В данном случае иных прерываний на таймере нет, так что побочных эффектов не возникнет.

Да и в прерывании TIMER0_OVF_vect это делать не имеет смысла, флаг сбрасывается при заходе в него. Это в армах надо принудительно сбрасывать флаг прерывания.
+1
они операцией |= также сбросятся
Это возможное действие или обязательно происходящее?
0
Если они там есть, то обязательно. А уж как они там появятся — это как таймеры работать будут.

Ох, тут же все флаги всех таймеров в одном регистре. Могут и помешать друг другу. Привык я к меге1280, где все таймеры независимы =D
0
А вот и ответ. Спасибо!
0
благодарю, дошло.
0
О, никогда не задумывался. Спасибо!
0
Мне кажется что товарищ писал свой код именно под ту железяку которую он описывал в статье, а не делал мультипереносимый код под что попало.
Если до этого возникло какое-нибудь другое прерывание, но мы его ещё не обработали, например, TOV1, то мы и его нечаянно сбросим = пропустим.
Думаю товарищ dcoder не дурак и умеет отлаживать подобные баги, если бы они вылезли на другом железе из использованием прерывания TOV1.
0
Разве то, что баг никогда не сработает — повод пихать в код явный баг? Тем более что откуда-то ж его все копипастят.
0
умеет отлаживать подобные баги
Самое противное в подобных багах, что они могут проявляться очень редко. Девайс с такой ошибкой может пройти тестирование и начать взбрыкивать через хз сколько времени. А вылавливать потом такое — та еще веселуха. Особненно паралельно с эксплуатацией.
+1
За что минусуете? Он совершенно прав.
0
За то что можно было сразу в трех словах описать почему так делать не надо, но он этого не сделал. Потом вот набежали добрые люди и таки рассказали в чем дело. Их плюсуют
+1
Достаточно заглянуть в даташит и немного подумать… Да и вопрос не так давно в сообществе уже обсуждался. К тому же, второе же сообщение Anatol80 поясняет что не так и как надо. Весь срач добавлен уже после этого сообшения.
0
Потом вот набежали добрые люди и таки рассказали в чем дело. Их плюсуют
Причем три раза. А проплюсовали только последнего.
0
А можно расширить диапазон задания температуры до +125 (чтобы он был от -55 до +125 по Цельсию)?
0
  • avatar
  • jes
  • 28 октября 2014, 13:21
пробежаться по файлу DS18B20_emulator.c и исправить все «99» на «125».
Или нужен сразу бинарник?
ЗЫ там заведен дефайн MAX_TEMP, но автор его не стал везде втыкать. Так что — Replace
0
пардон, там первый разряд отведен под знак и не заполняется цифрами
0
и для корректного отображения единицы в первом разряде придется в самом конце файла перед строчкой 346
_delay_ms(100);

добавить
if (tmp16>=100) display[0] = numbers[1];
0
Спасибо за подсказку.
Я вожусь сейчас с термометром на 2-х DS18B20, переделываю чужой исходник под себя.
Случайно обнаружил, что выводится температура только до +99.9, а выше — первые две цифры не выводятся.
Каждый раз нагревать и охлаждать термодатчик для проверки — муторно, эмулятор, как раз то, что надо.
В принципе уже все поправил в исходнике, но, на будущее эмулятор пригодится.
0
  • avatar
  • jes
  • 29 октября 2014, 08:25
Такой вопрос — как включить генерацию файла листинга в AVR Studio 4.12.460?
В исходник ставил команду .LIST, но, файла с расширением .LST — все равно нет.
Может быть где нибудь есть какая хитрая закладка в этой оболочке?
0
  • avatar
  • jes
  • 31 октября 2014, 15:41
В свойствах проекта должна быть галка «Generate List files». Или у вас нету такой? Расширение файла — LLS.
Или у вас ассемблер?
0
Спасибо, что подсказали, — нашел малюсенькую галочку «Create list file» в закладке «Assembler options».
Поставил ее, и появился файл с расширением LST.
Еще раз большое спасибо.
0
  • avatar
  • jes
  • 01 ноября 2014, 00:17
Какие фьюзы ставить подскажите пожалуйста!
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.