Сейчас, когда мы научились отправлять хосту любые произвольные данные из буфера, вернёмся немного назад, к седьмой части этой бесконечной статьи. В седьмой части мы остановились на том, что научились определять три типа пакетов, которые предназначены нам и на которые нам нужно что-то отвечать. Теперь, когда мы умеем что-то отвечать в принципе, можно думать, что отвечать в каждом конкретном случае.
Идея здесь вот какая, — в самом прерывании будем отправлять только пакеты подтверждения и заранее подготовленные ответы, а всю остальную работу будем выполнять вне прерывания. В противном случае нам просто может не хватить времени на обработку данных и подготовку ответа до прихода следующего пакета. А «отмалчиваться» в USB нельзя, иначе хост решит, что мы просто «отвалились». Лучше уж мы будем в случае чего отвечать на запросы пакетом Nak («не готов разговаривать»).
При этом для того, чтобы определять, что именно мы должны делать вне прерывания, придётся завести ещё одну переменную (назовём её скажем Action). В состоянии, когда ничего вне прерывания делать не нужно, — значение переменной Action будет равно нулю. Для её объявления, инициализации и сброса допишем следующий код:
в шапку:
;--- флаги действий вне прерывания .equ NothingAction = 0 ; ничего не делать .def Action = r23 ; что нам делать вне прерывания |
в процедуру Reset:
clr Action ; после сброса ничего не нужно делать |
Всё, переходим к конкретным ответам. Начнём с ответа на пакет данных, полученный в транзакции Setup (в нашей программе это место обозначено меткой ReciveSetupData). Как мы уже решили выше, в самом прерывании просто отправим пакет Ack и запишем в переменную Action значение, которое подскажет что мы должны делать после выхода из прерывания.
Кроме того, давайте скопируем данные входного буфера в какой-нибудь другой буфер (назовём его, например, рабочим) и вне прерывания будем работать с ним (поскольку данные во входном буфере могут быть затёрты при следующем прерывании).
Ещё нужно учесть, что при переходе от приёма пакета к обработке может получиться так, что во входной буфер не запишется последний полный или неполный байт (просмотрите ещё раз внимательно код приема пакета из 6-й части). Здесь нам нужно будет его дописать. Сам этот байт у нас хранится в регистре ShiftReg, поэтому нужно проследить, чтобы этот регистр не был затёрт ни при разборе пакетов, ни при отправке пакета подтверждения. Кроме ShiftReg нужно также не затереть temp0 (счётчик битов в последнем байте).
Для того, чтобы не затереть temp0 при разборе пакетов — придётся добавить ещё одну переменную (temp3) и переписать код разбора пакетов, заменив в нём temp0 на temp3. Далее, для того, чтобы не затереть temp0 и ShiftReg при отправке пакета — будем просто сохранять эти значения в стеке. В коде это всё выглядит так:
Дописываем в шапку объявление флага:
.equ SetupAction = 1 ; нужно обработать пакет данных транзакции Setup |
Дописываем код после метки RecieveSetupData:
rcall SendACK ; отвечаем ACK и завершаем транзакцию управления ldi Action,SetupAction ; отмечаем, что нам нужно обработать пакет данных от хоста rcall FinishRecieving ; готовим принятые данные к работе rjmp ExitFromIRQ ; выходим из прерывания |
Дописываем код в секцию дополнительных процедур:
SendAck: ldi ByteCount,2 ; передаём 2 байта ldi USBBufPtrYL, AckBuffer ; указатель на начало буфера clr OutBitstuffLength ; нет битовых вставок rcall SendUSBBuffer ; отправляем по USB ret |
Дописываем после метки SendUSBBuffer:
push temp0 push ShiftReg |
И дописываем в конце процедуры отправки пакета (перед ret):
pop ShiftReg pop temp0 |
Осталось описать процедурку сохранения последнего байта:
FinishRecieving: cpi temp0,7 ; счётчик настроен на 7 бит? breq NoRemainingBits ; нет незаписанных битов st X+,ShiftReg ; сохраняем последний байт и увеличиваем указатель NoRemainingBits: mov temp1,USBBufPtrXL ; temp1 = текущий адрес для сохранения subi temp1,InputBuffer ; temp1 = кол-во байт во входном буфере mov InputBufferLength,temp1 ; сохраняем это количество ldi USBBufPtrYL,WorkBuffer ; указатель на начало рабочего буфера ldi USBBufPtrXL,InputBuffer ; указатель на начало входного буфера CopyInputDataToWorkBuffer: ld temp0,X+ ; читаем байт из входного буфера st Y+,temp0 ; и копируем в рабочий dec temp1 brne CopyInputDataToWorkBuffer ; копируем ret |
Переходим к ответу на пакет данных, полученный в транзакции Out. Тут всё просто, — наш девайс очень простой и рулить им мы будем только через управляющие передачи. Поэтому, на транзакции Out будем отвечать только Ack или Nak (мол мы вас понимаем, но делать ничего не будем). Соответственно, остатки пакета нам тоже неинтересны и копировать пакет никуда не нужно. Nak будем отвечать в том случае, если установлен флаг обработки транзакции Setup, в противном случае — Ack. В коде это выглядит так:
После метки RecieveOutData добавляем следующий код:
cpi Action,SetupAction ; мы всё ещё обрабатываем Setup breq WeAreNotReady ; тогда мы не готовы к разговору WeAreReady: rcall SendACK ; отвечаем ACK clr Action ; указываем, что нам ничего не нужно делать rjmp ExitFromIRQ ; выходим из прерывания WeAreNotReady: rcall SendNAK ; отвечаем NAK rjmp ExitFromIRQ ; выходим из прерывания |
Процедура SendACK у нас уже есть, поэтому добавляем процедуру SendNAK:
SendNAK: ldi ByteCount,2 ; передаём 2 байта ldi USBBufPtrYL, NakBuffer ; указатель на начало буфера clr OutBitstuffLength ; нет битовых вставок rcall SendUSBBuffer ; отправляем по USB ret |
Осталось написать код, отвечающий за отправку данных хосту в транзакции IN. Здесь нам придётся ввести ещё пару флагов для регистра Action: ReadySendAnswer (готовы слать данные хосту) и PrepareAnswerCicl (готовим новую порцию ответа). Ответ мы будем готовить вне обработчика прерывания, в буфере OutputBuffer, так что в обработчике нам останется его только отправить в подходящий момент.
В коде это выглядит следующим образом:
AnswerToInRequest: cpi Action,ReadySendAnswer ; ответ готов? brne WeAreNotReady ; не готов rcall SendUSBAnswer ; готов, отправляем ldi Stage,InStage ; мы в транзакции IN ldi Action,PrepareAnswerCicl ; готовим новую порцию ответа в свободное время rjmp ExitFromIRQ ; выходим из прерывания |
С учётом добавленного сегодня, наш код целиком выглядит так:
.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 ReadySendAnswer = 2 ; готовы слать данные хосту .equ PrepareAnswerCicl = 3 ; готовим новую порцию ответа ;--------------------------------- ;--- распределение памяти -------- .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 .equ WorkBuffer = NakBuffer+2 ; начало рабочего буфера ;------------------------------------------------------ ;--- регистры ----------------------------------------- .def InputBufferLength = r11 ; количество байт во входном буфере .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 Action = r23 ; что нам делать вне прерывания .def temp3 = r24 ; temporary register .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 temp3,InputBuffer ; читаем из буфера PID lds temp1,InputBuffer+1 ; читаем из буфера адрес ;--- обе предыдущие команды не меняют флаги brne TestDataPacket ; если не ровно 3 байта (т.е. больше трёх, ибо ;--- меньше мы уже обработали) - это может быть только пакет данных, - прыгаем ;--- если ровно три байта - сначала проверяем не setup ли это? TestSetupPacket: cpi temp3,NRZI_rSETUPPID brne TestOutPacket ; если это не Setup, - проверяем, может это Out cp temp1,MyInAddress ; если Setup, то нам ли он? brne NoMyPacket ; если не нам - прыгаем ldi Stage,SetupStage ; ставим признак начала Setup-транзакции rjmp ExitFromIRQ ; выходим из прерывания ;--- проверяем, - не Out ли пакет мы получили TestOutPacket: cpi temp3,NRZI_rOUTPID brne TestInPacket ; если это не Out, - проверяем, может это In cp temp1,MyOutAddress ; если Out, то нам ли? brne NoMyPacket ; если не нам - прыгаем ldi Stage,OutStage ; ставим признак начала Out-транзакции rjmp ExitFromIRQ ; выходим из прерывания TestInPacket: cpi temp3,NRZI_rINPID brne TestDataPacket ; если это не IN - прыгаем cp temp1,MyInAddress ; если IN, то нам ли он? brne NoMyPacket ; если не нам - прыгаем ;**************************** ;--- приняли пакет IN (хост запрашивает данные), нужно что-то отвечать AnswerToInRequest: cpi Action,ReadySendAnswer ; ответ готов? brne WeAreNotReady ; не готов rcall SendUSBAnswer ; шлём ответ ldi Stage,InStage ; мы в транзакции IN ldi Action,PrepareAnswerCicl ; готовим новую порцию ответа в свободное время rjmp ExitFromIRQ ; выходим из прерывания ;**************************** TestDataPacket: cpi temp3,NRZI_rDATA0PID ; это DATA0? breq Data0Packet ; если да - прыгаем cpi temp3,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: rcall SendACK ; отвечаем ACK и завершаем транзакцию управления ldi Action,SetupAction ; отмечаем, что нам нужно обработать пакет данных транзакции Setup rcall FinishRecieving ; готовим принятые данные к работе rjmp ExitFromIRQ ; выходим из прерывания ;**************************** ;--- приняли пакет данных транзакции Out (эти пакеты мы не обрабатываем, но отвечаем Ack) RecieveOutData: cpi Action,SetupAction ; мы всё ещё обрабатываем Setup? breq WeAreNotReady ; тогда мы не готовы к разговору WeAreReady: rcall SendACK ; отвечаем ACK clr Action ; указываем, что нам ничего не нужно делать rjmp ExitFromIRQ ; выходим из прерывания WeAreNotReady: rcall SendNAK ; отвечаем NAK rjmp ExitFromIRQ ; выходим из прерывания ;**************************** ;********************************************************** ;--- дополнительные процедуры ----------------------------- ;--- Сброс USB (обнуление адресов, сброс состояний ...) USBReset: clr Stage ; начальная стадия ldi temp0,NRZI_rADDRESS0 ; начальный адрес mov MyInAddress,temp0 ; нулевой адрес mov MyOutAddress,temp0 ; нулевой адрес clr USBBufPtrXH ; обнуляем на случай, если там мусор после старта clr USBBufPtrYH ; обнуляем на случай, если там мусор после старта clr Action ; после сброса ничего не нужно делать rcall InitAckNakBuffers ; заполняем буферы с ответами Ack и Nak ret ;--- передача в линию подготовленного ответа заданной длины ;-- подготовка SendUSBAnswer: mov ByteCount,OutputBufferLength ; длина ответа без вставок - в счётчик ldi USBBufPtrYL,OutputBuffer ; указатель - на начало буфера ;-- сама передача SendUSBBuffer: push temp0 ; сохраняем кол-во бит в последнем байте push ShiftReg ; сохраняем значения бит в последнем байте 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 ; иначе меняем состояние линий на противоположное (остальные линии порта не меняются) ; 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 ; иначе меняем состояние линий на противоположное (остальные линии порта не меняются) ; 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-состояние pop ShiftReg pop temp0 ret ;--- инициализация буферов, содержащих ответы ACK и 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 ;--- отправка ответа ACK SendACK: ldi ByteCount,2 ; передаём 2 байта ldi USBBufPtrYL,AckBuffer ; указатель на начало буфера clr OutBitstuffLength ; нет битовых вставок rcall SendUSBBuffer ; отправляем по USB ret ;--- копируем принятые данные в рабочий буфер, сохраняя последний принятый байт FinishRecieving: cpi temp0,7 ; счетчик настроен на 7 бит? breq NoRemainingBits ; нет незаписанных битов st X+,ShiftReg ; сохраняем последний байт и увеличиваем указатель NoRemainingBits: mov temp1,USBBufPtrXL ; temp1 = текущий адрес для сохранения subi temp1,InputBuffer ; temp1 = количество байт во входном буфере mov InputBufferLength,temp1 ; сохраняем это количество ldi USBBufPtrYL,WorkBuffer ; указатель на начало рабочего буфера ldi USBBufPtrXL,InputBuffer ; указатель на начало входного буфера CopyInputDataToWorkBuffer: ld temp0,X+ ; читаем байт из входного буфера st Y+,temp0 ; и копируем в рабочий dec temp1 brne CopyInputDataToWorkBuffer ; копируем ret ;--- отправка ответа NAK SendNAK: ldi ByteCount,2 ; передаём 2 байта ldi USBBufPtrYL,NakBuffer ; указатель на начало буфера clr OutBitstuffLength ; нет битовых вставок rcall SendUSBBuffer ; отправляем по USB ret |
Всё, что нужно делать в обработчике прерывания мы разобрали, далее будем разбираться с тем, что нужно делать вне этого обработчика (подготовка ответов, обработка данных управляющих передач…).
- Часть 1. Основы.
- Часть 2. Как происходит передача данных по шине.
- Часть 3. Что должно уметь любое USB-устройство.
- Часть 4. Дескрипторы и классы.
- Часть 5. Программная реализация LS устройства USB. Схема.
- Часть 6. Программная реализация LS устройства USB. Физика и приём пакетов.
- Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.
- Часть 8. Программная реализация LS устройства USB. Передача по USB произвольного буфера и пакетов подтверждения.
- Часть 9. Программная реализация LS устройства USB. Продолжаем разбираться с принятыми пакетами.
Здравствуйте из 2021). Спасибо вам за огромный проделанный труд, очень хочется разобраться с USB на низком уровне. Не могли бы вы выложить готовый проект, или хотя бы указать что делать дальше, так как продолжения статьи я к сожалению не нашёл. Материал очень полезный для начинающих.
Продолжение статьи пока не существует и, честно говоря, я сомневаюсь в его необходимости. Когда всё это затевалось — на рынке не было дешёвых контроллеров с USB. Сейчас проще взять STM32 или его клон с готовыми библиотеками под full-speed usb, чем париться с 8-битными low-speed самоделками.
Если только из чисто академического интереса. Ну… может быть.
Спасибо больше за ответ! Да, как раз чисто академический интерес. Использование готовых библиотек не даёт понимания что там происходит внутри. А так можно было бы сопоставить чтение стандарта с практикой)