Особенности национального одноваре
... на языках высокого уровня, или "почему не работает 1wire?"
Несмотря на то, что интерфейс 1-wire достаточно прост и в общем случае хорошо описан, и уже реализован в библиотечных процедурах, в некоторых случаях приходится доставать большой шаманский бубен и плясать танец плодородия вокруг устройства.
Недавно и я столкнулся с подобной проблемой. Один мой сотрудник получил задание написать систему доступа в помещение, используя iButton (DS1990A - даласофский 1wire домофонный ключ), и далее, как развитие - подключить считыватель прокси (em-Marin) карт "CP-Z" фирмы IronLogic. Cей замечательный девайс хорош тем, что стоит очень не дорого и эмулирует DS1990A.
По идее, достаточно просто подключить CP-Z вместо считывателя для iBotton, и вот оно счастье! Однако не тут-то было. Считыватель упорно выдавал сплошные единицы. Все пляски с подтягивающими резисторами и прочей фигней не увенчались успехом. Стандартные библиотеки бодро выдавали 0xFF на любой запрос.
Поиск в интернете показал, что не у нас одних такие проблемы с этим считывателем. Господин Techmike на форуме Сахары уже поднимал означенную тему. Анализ осциллограмм и приведенного текста программ показал, что действительно возникают проблемы с таймингом. То есть, учитывая, что протокол 1-wire довольно критичен к временным интервалам, любая дополнительная задержка (или отсутствие таковой) приводит к фатальным результатам.
Сам протокол 1-wire очень хорошо и подробно описан господином ARV в статье Интерфейс 1-Wire на радиокоте. В частности достаточно хорошо видно, что при записи у нас имеются достаточно широкие допуски, а вот при чтении допуск таймингов доходит до 1 микросекунды. Запомним этот факт... и вернемся к нашему устройству. Учитывая, что стандартные библиотеки 1-wire со считывателем CP-Z не работают, было принято решение написать нечто свое, учитывающее повышенную требовательность протокола к соблюдению таймингов. Библиотека была написана на языке высокого уровня (Паскаль) и программа, ее использующая, была залита в AtTiny2313 работающего от внутреннего RC-генератора на 8 мегагерц. И все зашибись заработало!!! Однако, как выяснилось, радоваться было рано. При переносе на Mega8, работающую на частоте 1 МГерц все опять сломалось. Возврат к тиньке с понижением частоты до 1 мегагерца показал что дело не в чипе а в частоте на котором он работает... ниже 4 мегагерц протокол сыпался на любой микросхеме.
Надо отметить, что с точки зрения программиста на языке высокого уровня программа была написана безупречно. Однако, вспомним тот факт, что допуск таймингов в некоторых случаях не превышает 1 микросекунду.. это ровнехонько 1*10-6 секунды. То есть, ОДИН такт микропроцессора работающего на частоте в 1 МегаГерц. Для RISC процессоров AVR это время выполнения одной машинной (ассемблерной) команды. в лучшем случае. Справедливо возникло желание посмотреть, что же там такое компилятор делает?
И тут волосы встали дыбом. В общем: простейшее подергивание ногой с 1 на 0, и обратно, выполненное на паскале, вот так:
Port.Pin :=1; //включаем "питание" - 1 на выходе. Delay_us(dlLong); //ждем немного. хуже не будет Port.Pin :=0; //устанавливаем ноль на выходе
приводило к воооот такому коду:
; Port.Pin :=1; //включаем "питание" - 1 на выходе. MOV R27, R4 LDI R17, 1 TST R27 BREQ L__OneWireReset103 L__OneWireReset102: LSL R17 DEC R27 BRNE L__OneWireReset102 L__OneWireReset103: MOVW R30, R2 LD R16, Z OR R16, R17 MOVW R30, R2 ST Z, R16 ; Delay_us(dlLong); //ждем немного. хуже не будет LDI R16, 120 L__OneWireReset17: DEC R16 BRNE L__OneWireReset17 ; Port.Pin :=0; //устанавливаем ноль на выходе .... .... итд итп...
В общем, если процедура задержки еще худо-бедно обходится без вызовов подпрограмм, и просто четко отрабатывает определенное число циклов, не тратя время на обращение к стеку и прочую ерунду, то прямая адресация к ножке чипа выливается в кучу команд, да еще и цикл со сдвигом.
А вот кому сейчас по морде? кто там вякнул "паскаль отстой"? посмотрите тогда сами, во что выливается на Си конструкция типа:
BYTE ReadKey(BYTE *code, BYTE TM) { BYTE tcnt, Data, i; .................... if(PIN_TM & (1 << TM)) или CLRBIT(PORT_TM, TM); __delay_cycles(clkMhz * 8); SETBIT(PORT_TM, TM);
Убедились? О каком соблюдении тайминга может идти речь? А? В результате такого анализа удалось выявить источник проблемы, но не найти ее решение: переписывать кусок кода на ассемблер долго, и не очень надежно: фиг знает, где ты наступишь на лапу компилятору.
Единственный выход был - сократить количество машинных команд именно в критической по времени секции. В этом случае следует осознать, что хотя процессор AVR и имеет "на борту" команды снятия и установки конкретного бита, это не дает никаких преимуществ при использовании в качестве номера бита переменной. Также, хотя в языке высокого уровня имеются конструкции сдвига на несколько разрядов, в AVR такой роскоши нет. Команды сдвига сдвигают на один бит, и для сдвига на большее число бит компилятор вставляет цикл. Кажется что не беда.. но когда речь идет тайминге, сопоставимом со скоростью выполнения машинных команд, - это критично.
Итак. итожа говоренное. Необходимо вынести за скобки критической секции подготовку состояний порта. То есть:
вместо
{выставляем 1 на 2 микросекунды} port.x:=1; delay_us(2); port.x:=0; {на самом деле, задержка при 1Мгц составит до 20-40 микросекунд}
необходимо использовать следующую конструкцию:
{!!не критическая ко времени секция: готовим значения для всех ножек порта} temp0:=port ; temp0.x:=0; temp1:=port ; temp1.x:=1; {Критическая по времени секция} port:=temp1; delay_us(2) port:=temp0; {задержка составит от 2 до 8 микросекунд}
Также и с чтением: вместо конструкции
Rdata.i:=Pin.x;
дающей задержку ПЕРЕД чтением до 30 микросекунд необходимо использовать
{критическая секция} temp:=pin; {не критическая часть} Rdata.i:=temp.x;
Задержка перед чтением составляет около 2-3 микросекунд.
Таким образом, учтя особенности работы компилятора и системы команд AVR, можно добиться приемлемого результата.. кому интересны подробности - можно скачать исходники.
Автор: MayDay