Интерфейс USB. Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.

Часть 1. Основы.

Часть 2. Как происходит передача данных по шине.

Часть 3. Что должно уметь любое USB-устройство.

Часть 4. Дескрипторы и классы.

Часть 5. Программная реализация LS устройства USB. Схема.

Часть 6. Программная реализация LS устройства USB. Физика и приём пакетов.

Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.

Итак, в прошлой части мы написали код, обеспечивающий приём пакета USB в буфер SRAM. Теперь пришло время определить, что за пакеты мы приняли. Именно этим мы сегодня и займёмся, а потом уже будем решать, что нам с этим дальше делать

Сначала давайте подумаем, какие пакеты нас вообще будут интересовать. Из всего многообразия нас интересуют в первую очередь token-пакеты Setup, Out и In, отмечающие начало транзакций управления и передачи данных. Естественно, нас также будут интересовать пакеты данных Data0, Data1, содержащие сами передаваемые в транзакциях данные. Все остальные пакеты, включая пакеты подтверждения (handshake), пока оставим без внимания.

Теперь нужно придумать, как бы побыстрее идентифицировать эти пакеты, чтобы побыстрее, в случае необходимости, послать ответ хосту. Здесь поступим следующим образом, - вместо того, чтобы сначала декодировать из NRZI весь принятый буфер, а потом извлекать из полученных восстановленных данных PID и адрес, мы, наоборот, - будем хранить в программе все нужные идентификаторы сразу в закодированном виде (в NRZI) и точно также (в закодированном виде) будем хранить адрес устройства. При этом мы точно знаем, что ни в одном первом байте пакета битовые вставки не используются (просто потому, что там первые 4 бита - это сам идентификатор, а вторые 4 бита - его инверсная копия). Про адрес мы ничего подобного не знаем, но точно знаем, что битовые вставки не используются для начального адреса (он у нас ноль и по определению не может содержать 6 подряд единиц).

Для примера покажу как должен выглядеть первый байт setup-пакета в нашем буфере. Такой пакет имеет PID 1101. Дополняем PID его инверсной копией: 1101 0010. Все поля пакетов (кроме CRC) передаются младшим битом вперёд. Не байты, а именно поля! При приёме, в нашем сдвиговом регистре первые принятые биты оказываются старшими. Поэтому, чтобы получить правильный порядок бит первого байта setup-пакета (такой же, как в нашем буфере) - меняем порядок бит в каждом полубайте: 1011 0100. Ну и теперь осталось только закодировать этот байт в NRZI, с учётом того, что данные мы читаем с линии D+, состояние которой перед передачей этого байта точно было 1 (об этом мы в прошлый раз говорили). Получается вот что: 1000 1101. Аналогично можно вычислить как будут выглядеть первые байты для других пакетов.

Едем дальше. Поскольку в прерывании мы принимаем по одному пакету, а транзакция состоит из нескольких пакетов, то нам нужно будет их "склеивать", а для этого придётся выделить регистр, в котором будет храниться информация о текущей стадии обмена данными. Зная, какую часть транзакции мы только что приняли, мы будем понимать, какую часть мы теперь ожидаем.

В общем-то теперь можно начинать писать код, однако, поскольку в этой части алгоритм у нас более сложный, чем в предыдущей, то давайте всё-таки прежде нарисуем сам алгоритм:

алгоритм идентификации различных USB-пакетов

Теперь, с учётом этого алгоритма, наш код будет выглядеть так:

.device ATtiny2313
.include "tn2313def.inc"
.list
;--- определяем выводы портов ---
.equ  InputPort  = PINB  ; отсюда читаем
.equ  OutputPort = PORTB ; сюда пишем
.equ  Direction  = DDRB  ; выбор направления
.equ  USBDPlus   = 0     ; PB0 - DATA+
.equ  USBDMinus  = 1     ; PB1 - DATA-
.equ  LED0       = 2
.equ  LED1       = 3
.equ  LED2       = 4
;--- вспомогательные константы ---
.equ  USBPinMask = ~((1<<USBDMinus)|(1<<USBDPlus)) ; 0b11111100 - маска для D+/D-
;--- идентификаторы пакетов, которые мы будем принимать ---
;--- при приёме у нас байты получаются c обратным порядком бит ---
.equ  SETUPPID   = 0b11010010 ; PID+CHECK старшим битом вперёд
.equ  OUTPID     = 0b00011110
.equ  INPID      = 0b10010110
.equ  DATA0PID   = 0b00111100
.equ  DATA1PID   = 0b10110100

.equ  rSETUPPID  = 0b10110100 ; rPID+rCHECK - поля младшими битами вперёд
.equ  rOUTPID    = 0b10000111
.equ  rINPID     = 0b10010110
.equ  rDATA0PID  = 0b11000011
.equ  rDATA1PID  = 0b11010010

.equ  NRZI_rSETUPPID = 0b10001101 ; такой пакет мы принимаем (относительно D+)
.equ  NRZI_rOUTPID   = 0b10101111
.equ  NRZI_rINPID    = 0b10110001
.equ  NRZI_rDATA0PID = 0b11010111
.equ  NRZI_rDATA1PID = 0b11001010
;--- флаги различных стадий обработки данных
.equ  BaseStage  = 0 ; начальная стадия
.equ  SetupStage = 1 ; приняли маркер-пакет транзакции Setup
.equ  OutStage   = 2 ; приняли маркер-пакет транзакции Out
;---------------------------------
;--- распределение памяти --------
.equ  MaxUSBBytes = 13         ; максимальный размер буфера для "сырых" данных
.equ  StackTop    = RAMEND     ; вершина стека
.equ  InputBuffer = RAMEND-127 ; начало буфера "сырых" входных данных USB (0)
;------------------------------------------------------
;--- регистры -----------------------------------------
.def  MyOutAddress = r14 ; адрес для пакетов Out
.def  MyInAddress  = r15 ; адрес для пакетов In/Setup

.def  temp0    = r16 ; temporary register
.def  temp1    = r17 ; temporary register
.def  InputReg = r18 ; входной регистр (сюда читаем значения линий)
.def  ShiftReg = r19 ; сдвиговый регистр (сюда копим принимаемые биты)
.def  Stage    = r20 ; стадия обмена данными

.def  USBBufPtrXL = r26 ; XL - указатель на буфер USB
.def  USBBufPtrXH = r27 ; XH - указатель на буфер USB
;******************************************************
;-- начало программного кода
  .cseg
  .org 0
  rjmp  Init       ; переход на начало программы (вектор сброса)
;-- дальше идут вектора прерываний
  rjmp  IRQ_INT0   ; внешнее прерывание INT0
  .org  WDTaddr+1  ; программа начинается за таблицей векторов
;-----------------------------------------------------------
;-- начало программы (инициализация портов и переменных) ---
Init:
  ldi   temp0,StackTop
  out   SPL,temp0       ; инициализируем стек

  ldi   temp0,(1<<LED0)+(1<<LED1)+(1<<LED2)
  out   Direction,temp0 ; линии светодиодов - выходы
  ldi   temp0,0b11111011
  out   PORTD,temp0     ; включаем подтяжки на линиях порта D, кроме PD2(INT0)

  rcall USBReset        ; обнуление адресов, сброс состояний и т.д.

  ldi   temp0,0x0F      ; INT0 - прерывание по переднему фронту
  out   MCUCR,temp0
  ldi   temp0,1<<INT0 ; включаем внешнее прерывание INT0
  out   GIMSK,temp0
  sei                   ; разрешаем немаскированные прерывания
;--- Основной цикл ---
General_loop:
  sbis  InputPort,USBDminus ; если D- = 0, то возможно это Disconnect,
  rjmp  CheckUSBReset       ; в случае которого мы будем делать Reset
  rjmp  General_loop        ; если D- = 1, то считаем, что это IDLE
;--- Проверяем, не случился ли Disconnect ---
CheckUSBReset:
  ldi   temp0,255           ; будем отсчитывать 255 циклов
   ; если за это время не изменится состояние D- (так и останется 0),
   ; то считаем, что случился Disconnect и нужен Reset
WaitUSBReset:
  sbic  InputPort,USBDminus ; если D- всё ещё ноль - пропустить
  rjmp  General_loop
  dec   temp0               ; уменьшаем счётчик
  brne  WaitUSBReset        ; прыгаем, если не ноль
  rcall USBReset
  rjmp  General_loop
;--- Конец основного цикла ---
;**********************************************************
;--- Сброс USB (обнуление адресов, сброс состояний ...) ---
USBReset:
  clr   Stage        ; начальная стадия
  clr   MyInAddress  ; нулевой адрес
  clr   MyOutAddress ; нулевой адрес
  ret
;**********************************************************
;--- Внешнее прерывание INT0 (положительный фронт на D+) ---
IRQ_INT0:
  ldi   temp0,2      ; готовимся отсчитывать количество одинаковых битов
  ldi   temp1,2
   ; определяем момент смены бита
CheckDMOne:
  sbis  InputPort,USBDMinus
  rjmp  CheckDMOne
   ;--- теперь ждём единичный бит (в синхропоследовательности это в
   ;--- обязательном порядке будет состояние, когда D+ = 1)
   ;--- момент обнаружения этого бита будем использовать для синхронизации
   ;--- (от него начинаем отсчёт битовых интервалов, поэтому дальнейшие куски
   ;--- кода должны быть строго выверены по количеству тактов в отладчике)
CheckDPOne:
  sbis  InputPort,USBDPlus ; самое начало единичного бита
  rjmp  CheckDPOne         ; 2-й такт
DetectSyncEnd:
  sbis  InputPort,USBDPlus ; 3,4-й такты (D+=1) или  3-й такт (D+=0)
  rjmp  TestBit0           ; -/-                     4,5-й такты (D+=0)
TestBit1:
  ldi   temp0,2            ; 5-й такт (сбрасываем счётчик нулей)
  dec   temp1              ; 6-й такт
  nop                      ; 7-й такт (задержка, чтобы получить 8 тактов на бит)
  breq  USBBeginPacket     ; 8-й такт при temp1>0 или 8-й и 1-й при temp1=0
  rjmp  DetectSyncEnd      ; 1,2-й такты
TestBit0:
  ldi   temp1,2            ; 6-й такт (сбрасываем счётчик единиц)
  dec   temp0              ; 7-й такт
  nop                      ; 8-й такт
  brne	DetectSyncEnd      ; 1,2-й такты при temp0>0
   ;--- сюда попадаем, если два подряд бита равны нулю ---
   ;--- считаем, что это был глюк и просто выходим ---
ExitFromIRQ:
  ldi   ShiftReg,1<<INTF0
  out   GIFR,ShiftReg      ; сбрасываем флаг прерывания INT0
  reti
   ;--- начинаем принимать информационные биты ---
USBBeginPacket:	; сюда мы попадаем после 1-го такта первого информационного бита
   ;--- читаем первый информационный бит байта ---
  in    ShiftReg,InputPort ; и сразу пишем его в сдвиговый регистр
  nop                      ; 3-й такт
USBLoopBegin:
  ldi   temp0,6                 ; счётчик битов (для следующих 6 битов)
  ldi   temp1,MaxUSBBytes       ; счётчик байтов
  ldi   USBBufPtrXL,InputBuffer ; 6-й такт
  nop                           ; 7-й такт
USBLoopByte:
  nop                           ; 8-й такт
   ;--- читаем 2-7 информационные биты ---
USBLoop16:
  in    InputReg,InputPort  ; читаем значение битов D+/D-      | 1-й такт
  cbr   InputReg,USBPinMask ; сбрасываем все биты порта, кроме значений D+/D-
  breq  EndPacket           ; если оба нули - конец пакета     | 3-й такт, если не ноль
  ror   InputReg            ; вытесняем D+ в CF                | 4-й такт
  rol   ShiftReg            ; и пишем его в сдвиговый регистр  | 5-й такт
  dec   temp0               ; прочитали 7 битов?               | 6-й такт
  brne  USBLoop16           ; если нет - читаем (7,8-й такты)
  nop                       ; 8-й такт
   ;--- читаем последний бит байта ---
USBLoop7: 
  in    InputReg,InputPort  ; 1-й такт
  cbr   InputReg,USBPinMask ; сбрасываем все биты порта, кроме значений D+/D-
  breq  EndPacket           ; если оба нули - конец пакета     | 3-й такт, если не ноль
  ror   InputReg            ; вытесняем D+ в CF                | 4-й такт
  rol   ShiftReg            ; и пишем его в сдвиговый регистр  | 5-й такт
  ldi   temp0,7             ; настраиваем счётчик на приём 7 битов
  st    X+,ShiftReg         ; сохраняем в буфер принятый байт  | 7,8-й такт
   ;--- читаем первый бит очередного байта ---
USBLoop0:
  in    ShiftReg,InputPort  ; сразу пишем его в сдвиговый регистр
  cbr   InputReg,USBPinMask ; сбрасываем все биты порта, кроме значений D+/D-
  breq  EndPacket           ; если оба нули - конец пакета     | 3-й такт, если не ноль
  dec   temp0               ; 4-й такт
  dec   temp1               ; 5-й такт
  brne  USBLoopByte         ; 6,7-й такт, если не 0            | 6-й такт, если 0
   ;--- если буфер переполнен - остальные биты просто не записываем,
   ;--- (наш пакет по-любому меньше, но конца пакета нужно дождаться)
BufferOverrange:
  nop                       ; 7-й такт
  nop                       ; 8-й такт
  in    InputReg,InputPort  ; 1-й такт
  cbr   InputReg,USBPinMask ; сбрасываем все биты порта, кроме значений D+/D-
  breq  EndPacket           ; если оба нули - конец пакета  | 3-й такт, если не ноль, иначе 3,4
  nop                       ; 4-й такт
  rjmp  BufferOverrange     ; 5,6-й такт
   ;--- закончили принимать пакет и начинаем его анализировать ---
   ;--- попадаем сюда на 5-м такте первого или второго битового ---
   ;--- интервала SE0, теперь нужно как можно быстрее понять чего --- 
   ;--- от нас хотят и ответить (или не ответить, если хотят не от нас) ---
EndPacket:
  cpi   USBBufPtrXL,InputBuffer+3 ; приняли хотя бы 3 байта?
  brcs  ExitFromIRQ               ; если меньше - выходим
   ;*********************************************
   ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   ;--- а вот если да - начинаем разбирать пакеты
  lds   temp0,InputBuffer   ; читаем из буфера PID
  lds   temp1,InputBuffer+1 ; читаем из буфера адрес
   ;--- обе предыдущие команды не меняют флаги
  brne  TestDataPacket      ; если не ровно 3 байта (т.е. больше трёх, ибо
   ;--- меньше мы уже обработали) - это может быть только пакет данных, - прыгаем
   ;--- если ровно три байта - сначала проверяем не setup ли это?
TestSetupPacket:
  cpi   temp0,NRZI_rSETUPPID
  brne  TestOutPacket     ; если это не Setup, - проверяем, может это Out
  cp    temp1,MyInAddress ; если Setup, то нам ли он?
  brne  NoMyPacket        ; если не нам - прыгаем
  ldi   Stage,SetupStage  ; ставим признак начала Setup-транзакции
  rjmp  ExitFromIRQ       ; выходим из прерывания
   ;--- проверяем, - не Out ли пакет мы получили
TestOutPacket:
  cpi   temp0,NRZI_rOUTPID
  brne  TestInPacket       ; если это не Out, - проверяем, может это In
  cp    temp1,MyOutAddress ; если Out, то нам ли?
  brne  NoMyPacket         ; если не нам - прыгаем
  ldi   Stage,OutStage     ; ставим признак начала Out-транзакции
  rjmp  ExitFromIRQ        ; выходим из прерывания
TestInPacket:
  cpi   temp0,NRZI_rINPID
  brne  TestDataPacket    ; если это не IN - прыгаем
  cp    temp1,MyInAddress ; если IN, то нам ли он?
  brne  NoMyPacket        ; если не нам - прыгаем
AnswerToInRequest:
   ; 1) приняли пакет IN (хост запрашивает данные), нужно что-то отвечать
TestDataPacket:
  cpi   temp0,NRZI_rDATA0PID  ; это DATA0?
  breq  Data0Packet           ; если да - прыгаем
  cpi   temp0,NRZI_rDATA1PID  ; это DATA1?
  brne  NoMyPacket            ; если нет (это не DATA0 и не DATA1) - прыгаем
Data0Packet:
  cpi   Stage,SetupStage   ; мы в Setup-транзакции?
  breq  RecieveSetupData   ; значит мы приняли управляющие данные от хоста (Setup)
  cpi   Stage,OutStage     ; мы в Out-транзакции?
  breq  RecieveOutData     ; значит мы приняли какие-то данные от хоста
NoMyPacket:
  ldi   Stage,BaseStage    ; возвращаемся в начальную стадию
  rjmp  ExitFromIRQ        ; выходим
RecieveSetupData:
   ; 2) приняли пакет данных транзакции Setup (команду управления)
RecieveOutData:
   ; 3) приняли пакет данных транзакции Out
  reti

Вот и всё. Определять нам или не нам предназначены принятые пакеты, а также разделять "свои" пакеты по типам, мы теперь умеем. Осталось начиться правильно на "свои" пакеты отвечать. Это, как вы уже понимаете, ждёт нас в дальнейших статьях.

Часть 1. Основы.

Часть 2. Как происходит передача данных по шине.

Часть 3. Что должно уметь любое USB-устройство.

Часть 4. Дескрипторы и классы.

Часть 5. Программная реализация LS устройства USB. Схема.

Часть 6. Программная реализация LS устройства USB. Физика и приём пакетов.

Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.

radiohlam.ruтеорияинтерфейсы, протоколы

Понравилась статья? Поделись с друзьями!

Обсудить эту статью на форуме

 
Rambler's Top100 © 2009 - Материалы сайта охраняются законом об авторском праве