PORTS Forum
Ports and Interfaces => USB => Topic started by: NiGHTS on August 11, 2010, 02:22:08 pm
-
Hello. I have a problem with a simple USB concept which, after burning through many EPROM chips, I can't get 8 byte data to send from the firmware to the driver, but I have no problem sending it the other way around.
I am using the cypress CY7C63413C SSOP48, and I am using one of the earliest example source code from Jan Axelson, which is a modification of the logo.asm cypress example which drives an HID mouse. I am using libusb to communicate to the chip, and so far I have been successful writing 8 bytes of data on command via this function:
usb_control_msg(dev, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_ENDPOINT_OUT, 9, 0, 0, buf, 8, 10);
Reading 8 bytes is a whole other story. I have only been successful at putting data inside endpoint 1 from inside here:
Master_Loop:
mov A, [event_machine]
jacc event_machine_jumptable
no_event_pending:
mov A, [configuration]
cmp A, 01h
jnz no_event_task
mov A, [ep1_stall]
cmp A, FFh
jz no_event_task
; Write to ep1 here
mov A, 01h
mov [ep1_dmabuff0], A
mov [ep1_dmabuff1], A
mov A, 02h
or A, [ep1_data_toggle]
iowr EP_A1_Counter_Register
mov A, ACK_IN
iowr EP_A1_Mode_Register
mov A, EVENT_PENDING
mov [event_machine], A
event_task_done:
no_event_task:
jmp Master_Loop
But that is unreliable and delayed.
I've also tried creating an endpoint interrupt program for endpoint 1 but I have had no luck with it either.
Interrupt_Endpoint_1:
push A
iord EP_A1_Mode_Register
and A, EP_ACK
jz endpoint1_NAK
mov A, 00h
mov [Test7], A
mov A, [Test0]
mov [ep1_dmabuff0], A
mov A, [Test1]
mov [ep1_dmabuff1], A
mov A, 02h
or A, [ep1_data_toggle]
iowr EP_A1_Counter_Register
mov A, ACK_IN_STATUS_OUT
iowr EP_A1_Mode_Register
mov A, DATA_TOGGLE
xor [ep1_data_toggle], A
mov A, NO_EVENT_PENDING
mov [event_machine], A
jmp endpoint1_done
endpoint1_NAK:
mov A, NAK_IN
iowr EP_A1_Mode_Register
endpoint1_done:
pop A
reti
Please help me send data to the PC! I can't find any good way to send data :-\
-
If you are using Cypress sample code: Good luck. It is not quite good.
From the two code snippets it is hard to determine what is going wrong.
Basically what you should do is to have an interrupt routine that handles the endpoint when a data packet has been transmitted, checking for and clearing the ACK bit, inverting data toggle bit (you are transmitting, so it is up to you to do that), i.e.:
USB_EP1_ISR:
push A ; save accumulator on stack
iord EP_A1_Mode ; test whether we have an ACK bit
and A, ACK_BIT
jz doneEP1 ; do nothing if we don't have an ACK bit
mov A, NAKIN ; clear ACK bit
iowr EP_A1_Mode
iord EP_A1_Counter ; flip data 0/1 bit after
xor A, DATATOGGLE ; a successful data transfer
iowr EP_A1_Counter
doneEP1:
pop A ; restore accumulator from stack
reti ; return from interrupt
In your function to load the endpoint check if the endpoint is still in ACK_IN mode, if it is not you can load the endpoint FIFO and arm it for transmission.
-
USB_EP1_ISR:
push A ; save accumulator on stack
iord EP_A1_Mode ; test whether we have an ACK bit
and A, ACK_BIT
jz doneEP1 ; do nothing if we don't have an ACK bit
mov A, NAKIN ; clear ACK bit
iowr EP_A1_Mode
iord EP_A1_Counter ; flip data 0/1 bit after
xor A, DATATOGGLE ; a successful data transfer
iowr EP_A1_Counter
doneEP1:
pop A ; restore accumulator from stack
reti ; return from interrupt
Ok, so this code is executed when an "in" request occurs?
What i need to do is submit 8 bytes from the firmware at any random time to the PC. How can I tell the firmware, using the code you provided, to send [data0] through [data7] bytes whenever it feels like it? As for the PC driver, i will have a thread in a loop checking for any data from endpoint 1 and respond to new data when it returns more than zero bytes.
Also, what if data is loaded and ready to go and a new series of bytes needs to be sent? How could the firmware find out when to safely load ep1's buffer with new data from [data0] to [data7] so I don't overwrite the data that hasn't been sent yet?
I have burned through 14 cypress EPROM's without finding the right combination of code that would allow me to do this.
-
You should get one of the CY3654 emulators, they are flaky but make it a lot easier to debug.
In your main loop you check if the ACK_IN is set for the endpoint, if that is not the case the endpoint can be loaded with new data. You have to decide what strategy to use if the data is generated faster than you can send it to the host.
-
How do i trigger Endpoint 1 to send data?
-
You load the number of bytes into the count register and set the endpoints mode to ACK_IN.
Do you have a USB analyzer available? There are many more details that can prevent your host from properly receiving the data.
I do not answer such questions by email, helping here in the forum makes the information available for many people.
-
Ok, here is what I have so far:
;------------------------------------
; Endpoint 1 Processing
USB_EP1_ISR:
push A ; save accumulator on stack
iord EP_A1_Mode ; test whether we have an ACK bit
and A, ACK_BIT
jz doneEP1 ; do nothing if we don't have an ACK bit
mov A, NAKIN ; clear ACK bit
iowr EP_A1_Mode
iord EP_A1_Counter ; flip data 0/1 bit after
xor A, DATATOGGLE ; a successful data transfer
iowr EP_A1_Counter
doneEP1:
pop A ; restore accumulator from stack
reti ; return from interrupt
; ------------------------------------------------
; Called when firmware needs to send data to host
; Pass X=Number of bytes preloaded into EP1_Data bytes
; Returns X=0 if busy, X=unchanged if ok
SendData:
push A
push X
; If busy, exit with X=0
iord EP_A1_Mode
and A, ACK_IN
cmp A, ACK_IN
jnz SendData_Busy
; Set the endpoint mode to ACK_IN to kick start the transmission
mov A, ACK_IN
iowr EP_A1_Mode
; all done, all ok
pop X
pop A
ret
SendData_Busy:
; Set X=0 to indicate that nothing was sent
pop X
mov X, 00h
pop A
ret
Is the way that I am checking for ACK_IN okay? Is this concept correct? Can my firmware call this function and expect to send data using that exact interrupt code?
By the way, I will be using a USB analyzer from now on. That is a very good idea to use! But there is nothing else wrong. The only problem I have is my understanding of how to actually make the data leave the chip. So if I can get the code just right my driver should just work. Like I said, I have had limited success with using EP1 to transmit out of the chip, but my problem has always been 1) determining when it is safe to send data and 2) triggering the sending of the data. Everything else is perfectly working.
So if you can, please tell me if you see any problems with the code I just wrote and I will try it out as soon as my new order of 20 blank cypress chips comes in.
-
You also need to write the number of bytes you want to send to the counter register for the endpoint, make sure to preserve the toggle bit in this case.
Also you need to load your data to the transmit FIFO.
-
Ok, with what you just told me:
;----------------------------------------
; This code will be in the firmware somewhere...
FirmwareCode:
; load the data into the dma buffer
mov A, [Test0]
mov [ep1_dmabuff0], A
mov A, [Test1]
mov [ep1_dmabuff1], A
mov X, 02h ; 2 bytes of data pre-loaded
call SendData
; do other things...
nop
nop
ret
;------------------------------------
; Endpoint 1 Processing
USB_EP1_ISR:
push A ; save accumulator on stack
iord EP_A1_Mode ; test whether we have an ACK bit
and A, ACK_BIT
jz doneEP1 ; do nothing if we don't have an ACK bit
mov A, NAKIN ; clear ACK bit
iowr EP_A1_Mode
iord EP_A1_Counter ; flip data 0/1 bit after
xor A, DATATOGGLE ; a successful data transfer
iowr EP_A1_Counter
doneEP1:
pop A ; restore accumulator from stack
reti ; return from interrupt
; ------------------------------------------------
; Called when firmware needs to send data to host
; Pass X=Number of bytes preloaded into EP1_Data bytes
; Returns X=0 if busy, X=unchanged if ok
SendData:
push A
push X
; If busy, exit with X=0
iord EP_A1_Mode
and A, ACK_IN
cmp A, ACK_IN
jnz SendData_Busy
mov A, X ; bytes to send
or A, [ep1_data_toggle] ; Preserve toggle state?
iowr EP_A1_Counter
; Set the endpoint mode to ACK_IN to kick start the transmission
mov A, ACK_IN
iowr EP_A1_Mode
; all done, all ok
pop X
pop A
ret
SendData_Busy:
; Set X=0 to indicate that nothing was sent
pop X
mov X, 00h
pop A
ret
Here I added an example of the firmware calling the SendData function. It loads 2 test bytes to the endpoint 1 FIFO.
Also, right before flagging ACK_IN, It will copy the value in X into the counter as well as the toggle bit in its current state.
Am I doing it right? Does it look like it will work? Do you have any more advice for me?
Again, thank you immensely for your help so far!
-
Do not write to the FIFO before checking that the endpoint is idle, writing to the FIFO is disabled while the endpoint is armed to send data.
The toggle bit is not stored in memory by the interrupt routine, it is in the endpoint data count register.
-
Ok, based on what you told me, I've made some more changes:
;----------------------------------------
; This code will be in the firmware somewhere...
FirmwareCode:
; load the data into the dma buffer
mov A, [Test0]
mov [SendOut0], A
mov A, [Test1]
mov [SendOut1], A
mov X, 02h ; 2 bytes of data pre-loaded
call SendData
; do other things...
nop
nop
ret
;------------------------------------
; Endpoint 1 Processing
USB_EP1_ISR:
push A ; save accumulator on stack
iord EP_A1_Mode ; test whether we have an ACK bit
and A, ACK_BIT
jz doneEP1 ; do nothing if we don't have an ACK bit
mov A, NAKIN ; clear ACK bit
iowr EP_A1_Mode
iord EP_A1_Counter ; flip data 0/1 bit after
xor A, DATATOGGLE ; a successful data transfer
iowr EP_A1_Counter
doneEP1:
pop A ; restore accumulator from stack
reti ; return from interrupt
; ------------------------------------------------
; Called when firmware needs to send data to host
; Pass X=Number of bytes preloaded into EP1_Data bytes
; Returns X=0 if busy, X=unchanged if ok
SendData:
push A
push X
; If busy, exit with X=0
iord EP_A1_Mode
and A, ACK_IN
cmp A, ACK_IN
jnz SendData_Busy
; Update the counter with the number of bytes to send
mov A, X
mov [tmp], A
iord EP_A1_Counter
or A, [tmp] ; tmp contains number of bytes to send
iowr EP_A1_Counter
; Transfer the data from the buffer into the endpoint FIFO
mov X, 00h
SendData_Loop_Start:
mov A, [SendOut0 + X]
mov [ep1_dmabuff0 + X], A
inc X
mov A, X
cmp A, 08h
jnz SendData_Loop_Start
SendData_Loop_End:
; Set the endpoint mode to ACK_IN to kick start the transmission
mov A, ACK_IN
iowr EP_A1_Mode
; all done, all ok
pop X
pop A
ret
SendData_Busy:
; Set X=0 to indicate that nothing was sent
pop X
mov X, 00h
pop A
ret
So this time I have a buffer between Test(0-7) and ep1_dmabuff(0-7) called SendOut(0-7) which loads the test data only after it knows it can.
The next thing I changed was the part which updates the counter in the SendOut function. Now EP_A1_Counter's state is merely updated with the count, which I presume will preserve the state of the toggle bit?
Let me know if you see anything else I should add to this.
And again THANK YOU SO MUCH!!
-
I have tried out the code we worked on together on a live chip. I am having the same kind of problem that I've been having in the first place.
This is what my driver code looks like. Remember that I am using libusb for windows.
// Connect to the USB device
// On the USB Analyzer, these are the Blue events
char tmp[8];
usb_dev_handle *dev = NULL;
usb_init();
usb_find_busses();
usb_find_devices();
open_dev();
usb_set_configuration(dev, 1);
usb_claim_interface(dev, 0);
// request a call to SendData in the firmware which will load 8 bytes of test data into endpoint 1
// On the USB Analyzer, these are the Green events
tmp[0] = 0x05;
tmp[1] = 0x08;
tmp[2] = 0x01;
tmp[3] = 0x08;
tmp[4] = 0x00;
tmp[5] = 0x00;
tmp[6] = 0x00;
tmp[7] = 0x00;
usb_control_msg(dev, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_ENDPOINT_OUT, 9, 0, 0, tmp, 8, 10);
// Retrieve the test bytes from endpoint 1
// On the USB Analyzer, these are the Red events
for (int t=0;t<8;t++)
tmp[t] = 0;
y = usb_interrupt_read(dev, 0x81, tmp, 8, 5000);
// At this point I do in fact receive the test data, but I can no longer receive any data
// from endpoint 1 without resetting the device. Therefore, this only works once.
usb_release_interface(dev, 0);
usb_close(dev);
The problem is that I can only get data the first time from endpoint 1. After that, it no longer lets me receive data.
I used a USB Software Analyzer and these are the results. I have highlighted the events which correspond to the code in the driver above.
(http://img517.imageshack.us/img517/8813/uppic.jpg) (http://img517.imageshack.us/i/uppic.jpg/)
The last event with a status of UNSUCCESSFUL has this error: "USBD_STATUS_BUFFER_OVERRUN"
This might also be helpful to trying to figure out whats wrong. This is a report from libusb about the devices capabilities:
(http://img13.imageshack.us/img13/9384/picax.jpg) (http://img13.imageshack.us/i/picax.jpg/)
What do you think is wrong?
-
A software analyzer is close to useless for this kind of debugging, it does not show packets that are corrupted.
I am not familiar with libusb and I never test new devices on Windows first since this useless os is often a major problem when debugging USB. Likely you are running into one of its inherent problems, like Windows totally blocking reports that do not fit the corresponding report descriptor.
Since you are building a HID class device, why are you using libusb?
-
Not sure why I am using windows. The device is meant to be used in linux once I work out all the device bugs. What software analyzer do you reccomend that can run in ubuntu?
I am using the HID descriptors just for testing. Ultimately this will be a proprietary device, only USB device used, and on a dedicated system. I used the modified logo.asm code just to get my feet wet and understand the concepts of transmission.
By the way, I found out what was wrong with sending data. In the endpoint 1 descriptor, I had set the maximum packet size to 3. It was supposed to be 8. Changing that fixed the problem.
Now the only problem I am still having is trying to figure out why this peice of code does not work:
; If busy, exit with X=0
iord EP_A1_Mode
and A, ACK_IN
cmp A, ACK_IN
jnz SendData_Busy
This is from the most recent code I posted for the "SendData" function call. When I include this, It is always jumping to "SendData_Busy" even if the endpoint was never called. When I remove this, everything works fine. But by removing this code I can no longer keep SendData from being called when its in the middle of a transfer. What do you think I am doing wrong?
-
Your jump is wrong.
I recommend to use a hardware analyzer, a software analyzer can only show you a high level view, it will not show you errors in the toggle bit sequence, report size etc.
Make sure your report descriptor defines exactly the same data size as you actually send via your endpoint.
-
I reversed the jump. Is that correct?
; If busy, exit with X=0
iord EP_A1_Mode
and A, ACK_IN
cmp A, ACK_IN
jz SendData_Busy
By the way, I have another question. Lets say I have a 128us interrupt routine. If the processor is currently doing some work inside the 128us interrupt, what happens to interrupts endpoint 0 and 1? Will they be lost? Will the interrupt get executed after the timer routine is complete? Does that even matter? Basically, how does the cypress chip deal with multiple interrupts happening within the same time of each other? What is best programming practice for this type of situation? Should I try to avoid using the timer interrupt altogether?
-
Did you read the data sheet? Sounds like this is your first microcontroller project.
-
I have programmed with Gumstix and sheeva pc's but never directly to a microchip. I have read many documents related to this microcontroller, such as the datasheet (2 versions) and the cyasm compiler documentation. It still does not answer these kinds of questions.
Either way, I can see you have lost interest in helping me further. Nevertheless, Your help so far has been invaluable and for that I will be eternally grateful.
-
The interrupt section of the data sheet describes the behaviour in the situation with multiple interrupts.