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

Коробочка снабжена индикатором на три разряда, который показывает установленную температуру, двумя кнопками для установки нужного значения и портом 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
четырьмя кнопками для установки нужного значенияА на схеме — вдвое меньше.
Алсо, два вопроса
1) Почему бы не работать с 1-Wire через UART? Он бы взял тайминги на себя.
2) Почему бы не запихать этот функционал в уже сделанную коробочку — I2C/1-wire сниффер твоего же авторства?
А на схеме — вдвое меньше.ой :) на самом деле таки 2.
Почему бы не работать с 1-Wire через UART? Он бы взял тайминги на себя.интересная идея. хотя в данном случае от этого легче бы не стало. Но все равно про использование UART и в слейв-режиме я как-то не думал
Почему бы не запихать этот функционал в уже сделанную коробочку — I2C/1-wire сниффер твоего же авторства?а зачем? да и эта коробочка после покупки лог анализатора и раскуривания FTDI оказалась не нужна
О Господи!
Еще один исходник с сакраментальной фразой:
TIFR |= 1<<TOV0; //Сброс флага TOV0
Когда будем изучать матчасть, товарищи? :)
Еще один исходник с сакраментальной фразой:
TIFR |= 1<<TOV0; //Сброс флага TOV0
Когда будем изучать матчасть, товарищи? :)
Если до этого возникло какое-нибудь другое прерывание, но мы его ещё не обработали, например, TOV1, то мы и его нечаянно сбросим = пропустим.
Если до нашей операции TIFR |= 1<<TOV0; сработал к примеру флаг TOV1, но мы его ещё не обработали, то
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|1<<TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF, то есть сбросу ВСЕХ прерываний в данном регистре, даже ещё не обработанных.
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то для сброса ТОЛЬКО TOV0 правильной будет как раз запись TIFR = 1<<TOV0;
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|1<<TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF, то есть сбросу ВСЕХ прерываний в данном регистре, даже ещё не обработанных.
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то для сброса ТОЛЬКО TOV0 правильной будет как раз запись TIFR = 1<<TOV0;
Ситуация возможна только если в момент выполнения этого кода запрещены прерывания. Я правильно понимаю?
Если же флаг какого-либо прерывания поднимется в момент выполнения инструкции стоящей до операций чтение-модификация-запись и прерывания не запрещены в этот момент, произойдет обработка возникшего прерывания, а только потом мы дойдем до
Таким образом на практике потенциальную опасность данная конструкция несет, если она выполняется в теле обработчика прерывания. Ну или прерывания запрещены «искуственно».
Если же флаг какого-либо прерывания поднимется в момент выполнения инструкции стоящей до операций чтение-модификация-запись и прерывания не запрещены в этот момент, произойдет обработка возникшего прерывания, а только потом мы дойдем до
TIFR |= 1<<TOV0; //Сброс флага TOV0
Таким образом на практике потенциальную опасность данная конструкция несет, если она выполняется в теле обработчика прерывания. Ну или прерывания запрещены «искуственно».
Согласны ли Вы, что TIFR |= 1<<TOV0; несет потенциальную опасность, если она применена не в том месте и не в то время (по невнимательности или незнанию), а TIFR = 1<<TOV0; такой опасностью не обладает? Тем более когда ещё применять такую конструкцию, как не в теле обработчика прерывания или там, где прерывания запрещены.
Не в этом дело. Операцию |= нельзя применять для сброса битов в TIFR, т.к. это «Чтение-Модификация-Запись». Применяя |=, Вы можете несанкционированно сбросить биты остальные биты в TIFR: OCF1A, OCF1B, ICF1,OCF0B,TOV0, OCF0A, если они установлены.
Пример: установился ICF1. Когда Вы захотите сбросить свой TOV0, то своей |= Вы ПРОЧИТАЕТЕ ICF1(1), и тут же запишете эту 1 в ICF1. И он сбросится. А Вы про это и знать не будете. Это приведет к глюкам, которые никогда не отловите. Поэтому запомните золотое правило AVR: Сбрасывать TIFR только так: TIFR = (1<<TOV0);
Пример: установился ICF1. Когда Вы захотите сбросить свой TOV0, то своей |= Вы ПРОЧИТАЕТЕ ICF1(1), и тут же запишете эту 1 в ICF1. И он сбросится. А Вы про это и знать не будете. Это приведет к глюкам, которые никогда не отловите. Поэтому запомните золотое правило AVR: Сбрасывать TIFR только так: TIFR = (1<<TOV0);
• 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.
Если до нашей операции TIFR |= 1<<TOV0; сработал к примеру флаг TOV1, но мы его ещё не обработали, то
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то правильной будет как раз запись TIFR = 1<<TOV0;
TIFR |= 1<<TOV0 -> TIFR = 1<<TOV1|TOV0 -> Мы сбросим оба флага, и потеряем прерывание TOV1.
Так что в данном случае записи TIFR |= 1<<TOV0; эквивалентны TIFR = TIFR или TIFR = 0xFF
А поскольку запись нуля в этот регистр ничего не меняет, а сбрасывает только единица, то правильной будет как раз запись TIFR = 1<<TOV0;
Дело не в этом. Если в регистре имеются флаги иных прерываний (сравнение и т.д.), они операцией |= также сбросятся, и соответствующие прерывания не возникнут. В данном случае иных прерываний на таймере нет, так что побочных эффектов не возникнет.
Да и в прерывании TIMER0_OVF_vect это делать не имеет смысла, флаг сбрасывается при заходе в него. Это в армах надо принудительно сбрасывать флаг прерывания.
Да и в прерывании TIMER0_OVF_vect это делать не имеет смысла, флаг сбрасывается при заходе в него. Это в армах надо принудительно сбрасывать флаг прерывания.
- teplofizik
- 12 февраля 2013, 13:21
- ↑
- ↓
Если они там есть, то обязательно. А уж как они там появятся — это как таймеры работать будут.
Ох, тут же все флаги всех таймеров в одном регистре. Могут и помешать друг другу. Привык я к меге1280, где все таймеры независимы =D
Ох, тут же все флаги всех таймеров в одном регистре. Могут и помешать друг другу. Привык я к меге1280, где все таймеры независимы =D
- teplofizik
- 12 февраля 2013, 13:31
- ↑
- ↓
Мне кажется что товарищ писал свой код именно под ту железяку которую он описывал в статье, а не делал мультипереносимый код под что попало.
Если до этого возникло какое-нибудь другое прерывание, но мы его ещё не обработали, например, TOV1, то мы и его нечаянно сбросим = пропустим.Думаю товарищ dcoder не дурак и умеет отлаживать подобные баги, если бы они вылезли на другом железе из использованием прерывания TOV1.
пробежаться по файлу DS18B20_emulator.c и исправить все «99» на «125».
Или нужен сразу бинарник?
ЗЫ там заведен дефайн MAX_TEMP, но автор его не стал везде втыкать. Так что — Replace
Или нужен сразу бинарник?
ЗЫ там заведен дефайн MAX_TEMP, но автор его не стал везде втыкать. Так что — Replace
- andreich78
- 28 октября 2014, 15:45
- ↑
- ↓
пардон, там первый разряд отведен под знак и не заполняется цифрами
- andreich78
- 28 октября 2014, 15:50
- ↑
- ↓
и для корректного отображения единицы в первом разряде придется в самом конце файла перед строчкой 346
добавить
_delay_ms(100);
добавить
if (tmp16>=100) display[0] = numbers[1];
- andreich78
- 28 октября 2014, 16:20
- ↑
- ↓
Спасибо за подсказку.
Я вожусь сейчас с термометром на 2-х DS18B20, переделываю чужой исходник под себя.
Случайно обнаружил, что выводится температура только до +99.9, а выше — первые две цифры не выводятся.
Каждый раз нагревать и охлаждать термодатчик для проверки — муторно, эмулятор, как раз то, что надо.
В принципе уже все поправил в исходнике, но, на будущее эмулятор пригодится.
Я вожусь сейчас с термометром на 2-х DS18B20, переделываю чужой исходник под себя.
Случайно обнаружил, что выводится температура только до +99.9, а выше — первые две цифры не выводятся.
Каждый раз нагревать и охлаждать термодатчик для проверки — муторно, эмулятор, как раз то, что надо.
В принципе уже все поправил в исходнике, но, на будущее эмулятор пригодится.
Комментарии (40)
RSS свернуть / развернуть