Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

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

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

  • научимся что-то отвечать хосту в принципе, то есть напишем подпрограмму, которая будет передавать хосту буфер заданной длины с соблюдением всех таймингов. Причем, будем считать, что этот буфер содержит целиком пакет, который ранее уже дополнен битовыми вставками и нам нужно только его отправить по правилам NRZI.
  • научимся посылать пакеты подтверждения ACK и NAK

Для первой задачи нам понадобятся:

  • выходной буфер
  • переменная, содержащая длину выходного буфера без битовых вставок
  • переменная, содержащая количество битовых вставок в выходном буфере
  • счётчик переданных байт
  • счётчик переданных бит

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

Длина битового интервала, как вы помните, у нас составляет 8 тактов контроллера (кварц 12 МГц).

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

  • Итак, перед началом передачи у нас на линиях USB состояние IDLE. Мы переводим значения в выходном регистре порта в это состояние и меняем направление линий порта на выход.
  • Далее мы берём из буфера данные и начинаем по одному биту передавать их в линию, то есть просто устанавливаем состояния линий USB в зависимости от значения передаваемого бита. Правила здесь такие: если передаём единицу — состояния линий не изменяются, а если передаём ноль — состояния линий меняются на противоположные (NRZI-кодирование).
  • После того, как переданы все биты, мы должны сформировать на шине USB сигнал EOP, поскольку в буфере у нас содержался пакет целиком. Для LS это означает, что в течении двух битовых интервалов шина должна быть в состоянии SE0 (на обоих линиях низкий уровень).
  • Ну и в завершении всего, нужно снова перевести линии в состояние IDLE и переключить их на вход.

В коде это выглядит так:

добавляем описания в шапку:

Текст под катом

.equ  OutputBuffer = InputBuffer+MaxUSBBytes ; начало выходного буфера USB
 
.def OutBitstuffLength  = r12 ; количество битовых вставок в выходном буфере
.def OutputBufferLength = r13 ; длина ответа, подготовленного в выходном буфере
 
.def ByteCount          = r21 ; счётчик переданных байт
.def BitCount           = r22 ; счётчик переданных бит
 
.def USBBufPtrYL        = r28 ; YL - указатель на буфер USB
.def USBBufPtrYH        = r29 ; YH - указатель на буфер USB

[свернуть]

добавляем следующий код в секцию с дополнительными процедурами:

Текст под катом

  ; подготовка
SendUSBAnswer:
  mov   ByteCount,OutputBufferLength ; длина ответа без вставок - в счётчик
  ldi   USBBufPtrYL,OutputBuffer     ; указатель - на начало буфера
  ; сама передача
SendUSBBuffer:
  mov   temp0,ByteCount    ; загружаем в счётчик количество байт без учёта вставок
  ldi   temp1,~USBPinMask  ; mask for xoring (0b00000011), чтобы не менять остальные биты
  ld    ShiftReg,Y+        ; загружаем первый байт в регистр для передачи и увеличиваем указатель
  ; настраиваем линии USB на выход
  cbi   OutputPort,USBDPlus  ; DPlus = 0  : idle state of USB
  sbi   OutputPort,USBDMinus ; DMinus = 1 : idle state of USB
  sbi   Direction,USBDPlus   ; DPlus = output
  sbi   Direction,USBDMinus  ; DMinus = output

  in    InputReg,OutputPort  ; читаем Idle state, а также состояние остальных линий порта
SendUSBBufferLoop:
  ldi   BitCount,7           ; счётчик бит                        ; 4-й такт
SendUSBBufferByteLoop:
  nop                        ; для тайминга                       ; 5-й такт
  rol   ShiftReg             ; вытесняем передаваемый бит в CF    ; 6-й такт
                             ; (у нас уже реверсирован порядок,
                             ; поэтому передаём старшим вперёд)
  brcs  NoXORSend            ; если передаём 1 - не меняем состояние линий ; 7-й такт при CF=0,
                                                                           ; 7,8-й такты при CF=1
  eor   InputReg,temp1  ; иначе меняем состояние линий на противоположное  ; 8-й такт
NoXORSend:
  out   OutputPort,InputReg   ; выставляем передаваемый бит на линии USB   ; 1-й такт
  dec   BitCount              ; уменьшаем счётчик бит                      ; 2-й такт
  brne  SendUSBBufferByteLoop ; 3-й, если 0, 3,4-й, если не 0
  sbrs  ShiftReg,7            ; если последний передаваемый бит в байте = 1 - пропускаем
                              ; следующую команду   ; 4-й, если 0, 4,5-й, если не 0
  eor   InputReg,temp1        ; иначе меняем состояние линий USB на противоположное ; 5-й такт
NoXORSendLastBit:
  dec   temp0                 ; уменьшаем счётчик байт                     ; 6-й такт
  ld    ShiftReg,Y+           ; загружаем следующий байт для передачи      ; 7,8-й такт
  out   OutputPort,InputReg   ; выставляем передаваемый бит на линии USB   ; 1-й такт
  brne  SendUSBBufferLoop     ; если передали не все байты - прыгаем       ; 2-й, если все,
                                                                           ; 2,3-й если не все
  ; теперь нужно передать ещё OutBitstuffLength бит, то есть столько,
  ; сколько мы добавили к исходному буферу при NRZI-кодировании
  mov   BitCount,OutBitstuffLength ; счётчик бит = количество добавочных бит ; 3-й такт
  cpi   BitCount,0                 ; вставки не нужны?                     ; 4-й такт
  breq  NoBitstuffNeeded           ; если битовых вставок нет         ; 5-й если есть вставки,
                                                                      ; 5,6-й если нет
SendUSBBufferBitstuffLoop:
  rol   ShiftReg             ; вытесняем передаваемый бит в CF             ; 6-й такт
  brcs  NoXORBitstuffSend    ; если передаём 1 - не меняем состояние линий ; 7-й если CF=0
                                                                           ; 7,8-й если CF=1
  eor   InputReg,temp1 ; иначе меняем состояние линий USB на противоположное ; 8-й такт
NoXORBitstuffSend:
  out   OutputPort,InputReg  ; выставляем передаваемый бит на линии USB    ; 1-й такт
  nop                        ; для тайминга                                ; 2-й такт
  dec   BitCount             ; уменьшаем счётчик бит                       ; 3-й такт
  brne  SendUSBBufferBitstuffLoop  ; если передали не все биты             ; 4-й если все,
                                                                      ; 4,5-й если не все
  nop                              ; для тайминга                          ; 5-й такт
  nop                              ; для тайминга                          ; 6-й такт
NoBitstuffNeeded:
  nop                              ; для тайминга                          ; 7-й такт
  ; обе линии в 0 (формируем сигнал EOP, длительностью 2 бита)
  cbr   InputReg,(1< 2В, D+ < 2В)
  sbr   InputReg,(1<

[свернуть]

Переходим ко второй задаче, - отправка пакетов подтверждения.

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

Итак:

добавляем описание в шапку:

Текст под катом

.equ  SYNC         = 0b10000000  ; начало пакета
.equ  ACKPID       = 0b00101101
.equ  NAKPID       = 0b10100101
 
.equ  rSYNC        = 0b00000001
.equ  rACKPID      = 0b01001011
.equ  rNAKPID      = 0b01011010
 
.equ  NRZI_rSYNC   = 0b10101011
.equ  NRZI_rACKPID = 0b00100111
.equ  NRZI_rNAKPID = 0b00111001
 
.equ  AckBuffer    = OutputBuffer+MaxUSBBytes  ; начало буфера Ack
.equ  NakBuffer    = AckBuffer+2               ; начало буфера Nak

[свернуть]

добавляем код в секцию с дополнительными процедурами:

Текст под катом

InitAckNakBuffers:
  ; заполняем Ack-буфер
  ldi   temp0,rSYNC
  sts   AckBuffer+0,temp0
  ldi   temp0,rACKPID
  sts   AckBuffer+1,temp0
  ; заполняем Nak-буфер
  ldi   temp0,rSYNC
  sts   NakBuffer+0,temp0
  ldi   temp0,rNAKPID
  sts   NakBuffer+1,temp0
  ret

[свернуть]

и, наконец, добавляем вызов процедуры InitAckNakBuffers в конец процедуры USBReset:

Текст под катом

  rcall   InitAckNakBuffers  ; заполняем буферы с ответами Ack и Nak

[свернуть]

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

Текст под катом

.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  SYNC      = 0b10000000 ; начало пакета
.equ  ACKPID    = 0b00101101
.equ  NAKPID    = 0b10100101
.equ  SETUPPID  = 0b11010010 ; PID+CHECK старшим битом вперёд
.equ  OUTPID    = 0b00011110
.equ  INPID     = 0b10010110
.equ  DATA0PID  = 0b00111100
.equ  DATA1PID  = 0b10110100
.equ  ADDRESS0  = 0b00000000 ; начальный адрес = 0
 
.equ  rSYNC     = 0b00000001
.equ  rACKPID   = 0b01001011
.equ  rNAKPID   = 0b01011010
.equ  rSETUPPID = 0b10110100 ; rPID+rCHECK - поля младшими битами вперёд
.equ  rOUTPID   = 0b10000111
.equ  rINPID    = 0b10010110
.equ  rDATA0PID = 0b11000011
.equ  rDATA1PID = 0b11010010
.equ  rADDRESS0 = 0b00000000 ; начальный адрес, обратный порядок бит
 
  ;-- чтобы получить NRZI нужно учитывать, что перед началом передачи на линии D+
  ;-- висит ноль, а в конце последовательности SYNC - единица, т.е. начальным
  ;-- состоянием для SYNC будет ноль, а для других полей - единица
.equ  NRZI_rSYNC     = 0b10101011
.equ  NRZI_rACKPID   = 0b00100111
.equ  NRZI_rNAKPID   = 0b00111001
.equ  NRZI_rSETUPPID = 0b10001101 ; такой пакет мы принимаем (относительно D+)
.equ  NRZI_rOUTPID   = 0b10101111
.equ  NRZI_rINPID    = 0b10110001
.equ  NRZI_rDATA0PID = 0b11010111
.equ  NRZI_rDATA1PID = 0b11001001 ; здесь у меня была ошибка (0b11001010)
.equ  NRZI_rADDRESS0 = 0b01010101 ; поскольку все заголовки маркер-пакетов оканчиваются единицей
;--- флаги различных стадий обмена данными
.equ  BaseStage  = 0 ; начальная стадия
.equ  SetupStage = 1 ; стадия приёма транзакции Setup
.equ  OutStage   = 2 ; стадия приёма транзакции Out (данные от хоста)
.equ  InStage    = 3 ; стадия приёма транзакции In  (данные к хосту)
;--- флаги действий вне прерывания
.equ  NothingAction = 0 ; ничего не нужно делать
.equ  SetupAction   = 1 ; нужно обработать пакет данных транзакции Setup
;---------------------------------
;--- распределение памяти --------
.equ  MaxUSBBytes  = 13                       ; максимальный размер буфера для "сырых" данных
.equ  StackTop     = RAMEND                   ; вершина стека
.equ  InputBuffer  = RAMEND-127               ; начало буфера "сырых" входных данных USB (0)
.equ  OutputBuffer = InputBuffer+MaxUSBBytes  ; начало выходного буфера USB
.equ  AckBuffer    = OutputBuffer+MaxUSBBytes ; начало буфера Ack
.equ  NakBuffer    = AckBuffer+2              ; начало буфера Nak
;------------------------------------------------------
;--- регистры -----------------------------------------
.def  OutBitstuffLength  = r12 ; количество битовых вставок в выходном буфере
.def  OutputBufferLength = r13 ; длина ответа, подготовленного в выходном буфере
.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  ByteCount = r21 ; счётчик переданных байт
.def  BitCount  = r22 ; счётчик переданных бит
 
.def  USBBufPtrXL = r26 ; XL - указатель на буфер USB
.def  USBBufPtrXH = r27 ; XH - указатель на буфер USB
.def  USBBufPtrYL = r26 ; YL - указатель на буфер USB
.def  USBBufPtrYH = r27 ; YH - указатель на буфер 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
;--- Конец основного цикла ---
;**********************************************************
;--- Внешнее прерывание 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          ; если не нам - прыгаем
  ;--- приняли пакет IN (хост запрашивает данные), нужно что-то отвечать	
AnswerToInRequest:
 
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          ; выходим из прерывания
  ;--- приняли пакет данных транзакции Setup (команду управления)
RecieveSetupData:
 
  rjmp  ExitFromIRQ          ; выходим из прерывания
  ;--- приняли пакет данных транзакции Out
RecieveOutData:
 
  reti
;**********************************************************
;--- дополнительные процедуры -----------------------------
;--- Сброс USB (обнуление адресов, сброс состояний ...)
USBReset:
  clr   Stage                ; начальная стадия
  ldi   temp0,NRZI_rADDRESS0 ; нулевой
  mov   MyInAddress,temp0    ; начальный
  mov   MyOutAddress,temp0   ; адрес
  clr   USBBufPtrXH          ; обнуляем на случай, если там мусор после старта
  clr   USBBufPtrYH          ; обнуляем на случай, если там мусор после старта
 
  rcall InitAckNakBuffers ; заполняем буферы с ответами Ack и Nak
  ret
;--- передача в линию подготовленного ответа заданной длины
  ;-- подготовка
SendUSBAnswer:
  mov   ByteCount,OutputBufferLength ; длина ответа без вставок - в счётчик
  ldi   USBBufPtrYL,OutputBuffer     ; указатель - на начало буфера
  ;-- сама передача
SendUSBBuffer:
  mov   temp0,ByteCount      ; загружаем в счётчик количество байт без учёта вставок
  ldi   temp1,~USBPinMask    ; mask for xoring ( 0b00000011 ), чтобы не менять остальные биты
  ld    ShiftReg,Y+          ; загружаем первый байт в регистр для передачи и увеличиваем указатель
  ; настраиваем линии USB на выход
  cbi   OutputPort,USBDPlus  ; DPlus=0  : idle state of USB
  sbi   OutputPort,USBDMinus ; DMinus=1 : idle state of USB
  sbi   Direction,USBDPlus   ; DPlus = output
  sbi   Direction,USBDMinus  ; DMinus = output
 
  in    InputReg,OutputPort  ; читаем Idle state, а также состояние остальных линий порта
SendUSBBufferLoop:
  ldi   BitCount,7           ; счётчик бит                                 ; 4-й такт
SendUSBBufferByteLoop:
  nop                        ; для тайминга                                ; 5-й такт
  rol   ShiftReg             ; вытесняем передаваемый бит в CF             ; 6-й такт
                             ; (у нас уже реверсирован порядок, поэтому
                             ; передаём старшим вперёд)
  brcs  NoXORSend            ; если передаём 1 - не меняем состояние линий ; 7-й такт при CF=0,
                                                                           ; 7,8-й такты при CF=1
  eor   InputReg,temp1       ; иначе меняем состояние линий USB на противоположное ; 8-й такт
NoXORSend:
  out   OutputPort,InputReg  ; выставляем передаваемый бит на линии USB    ; 1-й такт
  dec   BitCount             ; уменьшаем счётчик бит                       ; 2-й такт
  brne  SendUSBBufferByteLoop; 3-й, если ноль; 3-й и 4-й, если не ноль
  sbrs  ShiftReg,7           ; если последний бит в байте = 1 - пропускаем команду ; 4-й, если 0,
                                                                           ; 4,5-й, если не 0
  eor   InputReg,temp1       ; иначе меняем состояние линий USB на противоположное ; 5-й такт
NoXORSendLastBit:
  dec   temp0                ; уменьшаем счётчик байт                      ; 6-й такт
  ld    ShiftReg,Y+          ; загружаем следующий байт для передачи       ; 7,8-й такт
  out   OutputPort,InputReg  ; выставляем передаваемый бит на линии USB    ; 1-й такт
  brne  SendUSBBufferLoop    ; если передали не все байты - прыгаем        ; 2-й, если все,
                                                                           ; 2,3-й, если не все
  ; теперь нужно передать ещё OutBitstuffLength бит, то есть столько,
  ; сколько мы добавили к исходному буферу при NRZI-кодировании
  mov   BitCount,OutBitstuffLength ; счётчик бит = количество добавочных бит ; 3-й такт
  cpi   BitCount,0           ; вставки не нужны ?                          ; 4-й такт
  breq  NoBitstuffNeeded     ; если битовых вставок нет   ; 5-й если есть вставки, 5,6-й если нет
SendUSBBufferBitstuffLoop:
  rol   ShiftReg             ; вытесняем передаваемый бит в CF             ; 6-й такт
  brcs  NoXORBitstuffSend    ; если передаём 1 - не меняем состояние линий ; 7-й такт, если CF = 0,
                                                                           ; 7,8-й, если CF=1
  eor   InputReg,temp1       ; иначе меняем состояние линий USB на противоположное ; 8-й такт
NoXORBitstuffSend:
  out   OutputPort,InputReg  ; выставляем передаваемый бит на линии USB    ; 1-й такт
  nop                        ; для соблюдения тайминга                     ; 2-й такт
  dec   BitCount             ; уменьшаем счётчик бит                       ; 3-й такт
  brne  SendUSBBufferBitstuffLoop ; если передали не все биты      ; 4-й такт, если все,
                                                                   ; 4,5-й, если не все
  nop                        ; для соблюдения тайминга                     ; 5-й такт
  nop                        ; для соблюдения тайминга                     ; 6-й такт
NoBitstuffNeeded:
  nop                        ; для соблюдения тайминга                     ; 7-й такт
  ; обе линии в 0 (формируем сигнал EOP, длительностью 2 бита)
  cbr   InputReg,(1<<USBDMinus)|(1<<USBDPlus)                              ; 8-й такт
  out   OutputPort,InputReg  ; передаём его в шину                         ; 1-й такт
  ldi   BitCount,4           ; готовимся передавать EOP длит-ю 2-х бита    ; 2-й такт
SendUSBWaitEOP:        ; 1 проход   2проход   3 проход   4 проход
  dec   BitCount       ; 3-й такт     6-й       9-й        12-й
  brne  SendUSBWaitEOP ; 4,5-й такт   7,8      10,11       13-й
  nop                  ; 14-й
  nop                  ; 15-й		
  ; переходим в состояние IDLE (D- > 2V, D+ < 2B)
  sbr   InputReg,(1<<USBDMinus)	; готовимся   ; 16-й такт
  out   OutputPort,InputReg     ; переходим
  ; переводим линии на вход
  cbi   Direction,USBDPlus      ; D+ - на вход
  cbi   Direction,USBDMinus     ; D- - на вход
  cbi   OutputPort,USBDMinus    ; D- в Z-состояние
  ret
;--- инициализация буферов, содержащих ответы ACK и NAK
InitAckNakBuffers:
  ; заполняем Ack-буфер
  ldi   temp0,rSYNC
  sts   AckBuffer+0,temp0
  ldi   temp0,rACKPID
  sts   AckBuffer+1,temp0
  ret
  ; заполняем Nak-буфер
  ldi   temp0,rSYNC
  sts   NakBuffer+0,temp0
  ldi   temp0,rNAKPID
  sts   NakBuffer+1,temp0
  ret

[свернуть]

Всё, устал, на сегодня хватит, продолжим в следующих частях...

  1. Часть 1. Основы.
  2. Часть 2. Как происходит передача данных по шине.
  3. Часть 3. Что должно уметь любое USB-устройство.
  4. Часть 4. Дескрипторы и классы.
  5. Часть 5. Программная реализация LS устройства USB. Схема.
  6. Часть 6. Программная реализация LS устройства USB. Физика и приём пакетов.
  7. Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.
  8. Часть 8. Программная реализация LS устройства USB. Передача по USB произвольного буфера и пакетов подтверждения.
  9. Часть 9. Программная реализация LS устройства USB. Продолжаем разбираться с принятыми пакетами.

Добавить комментарий