Особенности национального одноваре

... на языках высокого уровня, или "почему не работает 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