В предыдущей части мы научились разбирать пакеты по типам и остановились на том, что определили три типа пакетов, которые предназначены нам и на которые нам нужно что-то отвечать. Учиться отвечать давайте начнём с общего, а не с частностей, а именно:
- научимся что-то отвечать хосту в принципе, то есть напишем подпрограмму, которая будет передавать хосту буфер заданной длины с соблюдением всех таймингов. Причем, будем считать, что этот буфер содержит целиком пакет, который ранее уже дополнен битовыми вставками и нам нужно только его отправить по правилам 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. Основы.
- Часть 2. Как происходит передача данных по шине.
- Часть 3. Что должно уметь любое USB-устройство.
- Часть 4. Дескрипторы и классы.
- Часть 5. Программная реализация LS устройства USB. Схема.
- Часть 6. Программная реализация LS устройства USB. Физика и приём пакетов.
- Часть 7. Программная реализация LS устройства USB. Разбираем пакеты по типам.
- Часть 8. Программная реализация LS устройства USB. Передача по USB произвольного буфера и пакетов подтверждения.
- Часть 9. Программная реализация LS устройства USB. Продолжаем разбираться с принятыми пакетами.