Опрос ключей iButton по 1-wire на ATmega16a

Dec 12, 2015 14:17

Понадобилось тут читать ключи йаКнопко. Ну тут всё очень просто. Берём КодеВиженАВР, в визарде выбираем 1 Wire Bus Interface, выбираем ногу, в коде пропишется #include <1wire.h>, после чего можно спокойно пользоваться функциями из этого самого хидера.

Нет, на самом деле всё было немного не так. А как именно - можно прочитать под катом.

Дело в том, что опрашивать нужно сразу несколько линий. Но почему бы не посадить все датчики на одну линию, ведь протокол 1-wire это позволяет? Да, позволяет. Но нам нужно контролировать не только сам ключ, но и проверять к какому датчику его приложили (на самом деле у меня читаются не ключи-таблетки, а RFID метки через ридер, который уже прикидывается iButton ключом, но суть та же). А ещё хотелось вручную реализовать протокол. Не знаю, что было основным, но в любом случае использовать готовое не представлялось возможным.

Как-то так сложилось, что микроконтроллеры были для меня всегда "где-то рядом". И на волне "мега уже не торт" несколько лет назад я, вроде как, решил, что не буду их использовать, а буду осваивать стм8. Но в режиме "девайс нужен вчера" пришлось брать что по проще и делать на меге 16.

Недостатка информации по протоколу 1-wire в сети не наблюдается. Под конец разработки даже оказалось, что всё придумано до нас достаточно было почитать документацию (например, вот есть перевод: http://microsin.net/programming/avr/avr318-dallas-1-wire-master.html).

Итак, суть интерфейса 1-wire - в общем проводе, который подтянут к питанию и который можно класть на землю. Правильнее было бы настроить ноги как открытый коллектор, но это в стм32 широкий выбор настроек, а тут всего два бита на ногу. Впрочем, и среди них нашлось хорошее решение. Если записать в PORT 0, то можно управлять ногой через DDR - при 1 нога становится выходом с логическим значением 0, при 0 нога становится входом без подтяжки и внешним резистором подтягивается к питанию - "запись" единицы или читаем из PIN. Да, получилась инверсия, но это не критично. Далее, датчиков у нас несколько (для круглого счёта - 8), все они сидят на одном порту. Передаваемые данные для всех одинаковые. Следовательно, можно дёргать весь порт целиком, а не отдельные ноги, и обойтись без побитовых операций. У меня использовался портА. Соответственно, чтобы передать в линии 0, пишем DDRA=0xff;, чтобы отпустить линии - DDRA=0x00;, читаем из PINA.

Как управлять выводами разобрались. Теперь нужно, чтобы там было что-то осмысленное. Любой обмен информацией начинается с импульса Reset. Просто кладём линии на землю DDRA=0xff и через 480мкс отпускаем DDRA=0x00. Если на шине кто-то есть, то он ответит импульсом Presence. Через 70мкс просто прочитаем порт, чтобы запомнить, есть ли там кто-нибудь persent=~PINA. Теперь мы уже можем класть на землю не весь порт, а только те пины, где кто-то есть. То есть писать DDRA=persent. Далее нужно послать команду на чтение ROM (id/серийный номер/e.t.c.) - байт 0x33. Запишем в переменную и в цикле будем отправлять по одному биту и сдвигать переменную (двигаем вправо, так как сначала идут младшие разряды). Теперь самое время разобраться, как передаются биты. Любая передача данных происходит в тайм-слотах. Начинается он с того, что мастер притягивает линию к земле. Далее, если он хочет передать 1, то почти сразу отпускает линию и она притягивается к 1, если 0, то держит линию до конца тайм-слота. Принцип один и тот же, отличаются только тайминги (конкретные значения можно взять по ссылке выше). Если мастер хочет прочитать бит, то он не надолго притягивает линию к земле и отпускает (точно также, как и при передаче 1), а вот дальше уже всё зависит от слейва. Если он хочет передать 0, то держит линию на земле на протяжении тайм слота, если хочет передать 1, то просто ничего не делает и линия подтягивается к 1. Остаётся только прочитать PINA. Так мы получим 1 байт - эдакий массив бит от каждого слейва. Который уже можно раскидать по разным переменным.

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

#asm("cli") //выключаем прерывания на время опроса
PORTA=0x00; //пишем в ПОРТ 0, чтобы он работал в режиме вход без подтяжки/выход со значением 0
DDRA=0xff; //пишем в ДДР единицы - ноги становятся выходами и кладутся на землю
delay_us(480); //ждём 480 микросекунд (импульс Reset)
DDRA=0x00; //делаем ноги входом - они притягиваются к питанию
delay_us(70); //ждём 70 микросекунд и
persent=~PINA; //проверяем, на каких ногах есть импульс Persent
delay_us(410);
PORTC=persent;
buf=0x33;//команда чтения ключа
for(i=0;i<8;i++){//будем отправлять в цикле по 1 биту
if (buf&0x01){//если отправляем 1, то у нас одни тайминги
DDRA=persent;
delay_us(6);//pause 6mks
DDRA=0x00;
delay_us(64);//pause 64mks
}
else{//а если 0, то другие
DDRA=persent;
delay_us(60);//pause 60mks
DDRA=0x00;
delay_us(10);//pause 10mks
}
buf=buf>>1;//готовим следующий бит
}
for(j=0;j<8;j++){//далее нам нужно прочитать 8 байт
for(k=0;k<8;k++)
keys[k][j]=0;//готовим место для очередного байта
for(i=0;i<8;i++){
DDRA=persent;//начало тайм слота
delay_us(6);//Pause 6mks
DDRA=0x00;//отпускаем линию
delay_us(9);//Pause 9mks
buf=PINA;//и смотрим, что что ответил
delay_us(55);//Pause 55mks
for(k=0;k<8;k++){//раскладываем полученные биты по местам
keys[k][j]>>=1;
if(buf&0x01)
keys[k][j]|=0x80;
buf>>=1;
}
}
}
#asm("sei")//включаем прерывания

ibutton, кодинг, atmega, 1-wire

Previous post Next post
Up