• AVR Freaks

Helpful ReplyUSB Bulk transfer on PIC18F4550

Page: 12 > Showing page 1 of 2
Author
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
2010/01/02 10:39:56 (permalink)
0

USB Bulk transfer on PIC18F4550

Folks,
 
I am trying to transfer a reasonable amount of data from a PIC18F4550 to and from a PC.  At first I thought I would try isochronous
transfer, but I wasn't winning there so I decided to try bulk transfer.  I'm still not winning.
 
Control transfer works fine on EP0, so I want to use EP1 IN and OUT for bulk transfers.  I think I have set them up correctly,
the PIC enumerates and I can see two open pipes (with USBTrace).  When I run a test program I can claim the interface
but both usb_bulk_read and usb_bulk_write give a "-116" timeout error.
 
I am not at all sure how to ensure that DATA0/1 is correctly set.  I see that the first (bulk?) transfer is meant to be sent
with DATA0, but I may have that wrong.  How can I correctly initialise that (at both ends)?
 
My PIC code is written in assembler and is loosely based on Brad Minch's code.  I am using libusb0.1.12 for win32.
 
Does anyone have a simple example that transfers a few bytes of data to and from a PIC using bulk transfer?  I've been
at this for days now and it's getting really dispiriting.
 
Any help would be much appreciated.
 
Martin
#1
chinzei
Super Member
  • Total Posts : 2250
  • Reward points : 0
  • Joined: 2003/11/07 12:39:02
  • Location: Tokyo, Japan
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/03 03:01:38 (permalink) ☄ Helpfulby FoxVK 2015/09/25 04:05:41
+2 (1)
On the device side, there is no difference between interrupt and bulk endpoints (EPs) of their handling.
Therefore, Brad Minch's HID implementation gives good start point for you.
This example equips an interrupt IN EP for HID.
This interrupt EP is converted into bulk EP easily, just by changing the endpoint descriptor.

Brad Minch's course materials,
http://pe.ece.olin.edu/ece/projects.html

PIC18F2455 - HID implementation
http://pe.ece.olin.edu/ece/projects/lab2_18F2455.zip



The points of bulk and interrupt EP handling are summarized as follows,

1) Endpoint registration - endpoint descriptor(s)
 Device declares EP(s) usage to host with endpoint descriptor(s) on its config descriptor set.

2) Bus reset
 Disable all EPs other than the default one

3) Set_Configuration (or Set_Interface, if any)
 Enable all EPs used on its configuration
Also, the EPs are initialized.
- Data toggle is reset to "0" (DATA0) on bulk and interrupt EPs

4) Endpoint HALT handling over standard requests
 Set_Feature( ENDPOINT_HALT ), Clear_Feature( ENDPOINT_HALT ), Get_Status( ENDPOINT )
- Clear_Feature( ENDPOINT_HALT ) resets Data toggle to "0" for specified EP, too

5) Run EPs
 After successful transaction, Data toggle switches between 0 and 1 (DATA0/DATA1)


[ Side notes ]
When 1), 3), 5) are implemented at least, your device works as usual.
2) and 4) are for completeness of USB spec compliance.

Full-speed isoc EP differs from bulk and interrupt EPs on these points.
- No Data toggle - fixed in DATA0
- No STALL (HALT) handshake
- Synchronization to SOF (Start Of Frame) event (hardware interrupt)



Let's pick up these points from Brad Minch's lab2.asm

1) Endpoint descriptor
Change the endpoint descriptor(s) to the config descriptor set.
- bmAttributes: interrupt 0x03, bulk 0x02
- wMaxPacketSize: for full-speed bulk EP, this value is limited to 8, 16, 32 or 64
- bInterval: for full-speed bulk EP, this value is ignored. Usually, set to 0

Also, when you add extra endpoints,
- Config descriptor: wTotalLength - tune the total length of the descriptor set by the size of endpoint descriptors
- Interface descriptor: bNumEndpoints - tune the number of endpoints which belong to this interface

Descriptor_begin
...
...
Configuration1
            db          0x09, CONFIGURATION ; bLength, bDescriptorType
            db          0x22, 0x00          ; wTotalLength (low byte), wTotalLength (high byte)  <--------
            db          NUM_INTERFACES, 0x01    ; bNumInterfaces, bConfigurationValue
            db          0x00, 0xA0          ; iConfiguration (none), bmAttributes
            db          0x32                ; bMaxPower (100 mA)

            db          0x09                ; bLength (Interface1 descriptor starts here)
            db          INTERFACE, 0x00     ; bDescriptorType, bInterfaceNumber
            db          0x00, 0x01          ; bAlternateSetting, bNumEndpoints (excluding EP0)   <--------
            db          0x03, 0x01          ; bInterfaceClass (HID code), bInterfaceSubClass (Boot subclass)
            db          0x01, 0x00          ; bInterfaceProtocol (Keyboard protocol), iInterface (none)
HID1
            ...
            ...
            db          0x07                ; bLength (Endpoint1 descritor starts here)
            db          ENDPOINT, 0x81      ; bDescriptorType, bEndpointAddress (EP1 IN)
            db          0x03, 0x08          ; bmAttributes (Interrupt), wMaxPacketSize (low byte)
            db          0x00, 0x0A          ; wMaxPacketSize (high byte), bInterval (10 ms)



2) Bus reset
PIC USB engine (SIE) raises URSTIF on UIR register, when it has received bus reset.
Brad Minch's code handles bus rest gracefully - all endpoints are disabled, and EP0 is initialized.
No modification is required, here.

ServiceUSB
            select
                ...
                ...
                caseset UIR, URSTIF, ACCESS
                    banksel     USB_curr_config
                    clrf        USB_curr_config, BANKED
                    bcf         UIR, TRNIF, ACCESS      ; clear TRNIF four times to clear out the USTAT FIFO
                    bcf         UIR, TRNIF, ACCESS
                    bcf         UIR, TRNIF, ACCESS
                    bcf         UIR, TRNIF, ACCESS
                    clrf        UEP0, ACCESS            ; clear all EP control registers to disable all endpoints
                    clrf        UEP1, ACCESS
                    clrf        UEP2, ACCESS
                    clrf        UEP3, ACCESS
                    clrf        UEP4, ACCESS
                    clrf        UEP5, ACCESS
                    clrf        UEP6, ACCESS
                    clrf        UEP7, ACCESS
                    clrf        UEP8, ACCESS
                    clrf        UEP9, ACCESS
                    clrf        UEP10, ACCESS
                    clrf        UEP11, ACCESS
                    clrf        UEP12, ACCESS
                    clrf        UEP13, ACCESS
                    clrf        UEP14, ACCESS
                    clrf        UEP15, ACCESS
                    banksel     BD0OBC
                    movlw       MAX_PACKET_SIZE
                    movwf       BD0OBC, BANKED
                    movlw       low USB_Buffer          ; EP0 OUT gets a buffer...
                    movwf       BD0OAL, BANKED
                    movlw       high USB_Buffer
                    movwf       BD0OAH, BANKED          ; ...set up its address
                    movlw       0x88                    ; set UOWN bit (USB can write)
                    movwf       BD0OST, BANKED
                    movlw       low (USB_Buffer+MAX_PACKET_SIZE)    ; EP0 IN gets a buffer...
                    movwf       BD0IAL, BANKED
                    movlw       high (USB_Buffer+MAX_PACKET_SIZE)
                    movwf       BD0IAH, BANKED          ; ...set up its address
                    movlw       0x08                    ; clear UOWN bit (MCU can write)
                    movwf       BD0IST, BANKED
                    clrf        UADDR, ACCESS           ; set USB Address to 0
                    clrf        UIR, ACCESS             ; clear all the USB interrupt flags
                    movlw       ENDPT_CONTROL
                    movwf       UEP0, ACCESS            ; EP0 is a control pipe and requires an ACK
                    movlw       0xFF                    ; enable all error interrupts
                    movwf       UEIE, ACCESS
                    banksel     USB_USWSTAT
                    movlw       DEFAULT_STATE
                    movwf       USB_USWSTAT, BANKED
                    movlw       0x01
                    movwf       USB_device_status, BANKED   ; self powered, remote wakeup disabled
#ifdef SHOW_ENUM_STATUS
                    movlw       0xE0
                    andwf       PORTB, F, ACCESS
                    bsf         PORTB, 1, ACCESS        ; set bit 1 of PORTB to indicate Powered state
#endif
                    break



3) Set_Configuration
Brad Minch's code implements EP1 IN endpoint here.
a) Initialize BD (Buffer Descriptor) for EP1 IN
- BDnCNT (BD1IBC): set to the size of packet
- BDnADRL,BDnADRH (BD1IAL, BD1IAH): assigned to the buffer address
- BDnSTAT (BD1IST): initialized to 0x48, DTS and DTSEN bits are "1"
b) Enable the endpoint on UEP1 register

For other endpoint(s) including OUT EP, you'll replace or add its setting
at the ";; -------- EP1 IN set up --------" portion of this code.

note: Brad Minch's code toggles DTS bit just before passing EP ownership to SIE,
as we'll see it on next "5) Run EPs" section
Therefore, DTS bit is set to "1" instead of "0" here.


StandardRequests
            movf        USB_buffer_data+bRequest, W, BANKED
            select
                ...
                ...
                case SET_CONFIGURATION
                    ifl USB_buffer_data+wValue, <=, NUM_CONFIGURATIONS
                        clrf        UEP1, ACCESS        ; clear all EP control registers except for EP0
                                                        ; to disable EP1-EP15 prior to setting configuration
                        clrf        UEP2, ACCESS
                        clrf        UEP3, ACCESS
                        clrf        UEP4, ACCESS
                        clrf        UEP5, ACCESS
                        clrf        UEP6, ACCESS
                        clrf        UEP7, ACCESS
                        clrf        UEP8, ACCESS
                        clrf        UEP9, ACCESS
                        clrf        UEP10, ACCESS
                        clrf        UEP11, ACCESS
                        clrf        UEP12, ACCESS
                        clrf        UEP13, ACCESS
                        clrf        UEP14, ACCESS
                        clrf        UEP15, ACCESS
                        movf        USB_buffer_data+wValue, W, BANKED
                        movwf       USB_curr_config, BANKED
                        select
                            case 0
                                movlw       ADDRESS_STATE
                                movwf       USB_USWSTAT, BANKED
#ifdef SHOW_ENUM_STATUS
                                movlw       0xE0
                                andwf       PORTB, F, ACCESS
                                bsf         PORTB, 2, ACCESS
#endif
                                break
                            default
                                movlw       CONFIG_STATE
                                movwf       USB_USWSTAT, BANKED

    ;; -------- EP1 IN set up, from here ---------
                                movlw       0x08
                                banksel     BD1IBC
                                movwf       BD1IBC, BANKED          ; set EP1 IN byte count to 8
                                movlw       low (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
                                movwf       BD1IAL, BANKED          ; set EP1 IN buffer address
                                movlw       high (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
                                movwf       BD1IAH, BANKED
                                movlw       0x48
                                movwf       BD1IST, BANKED          ; clear UOWN bit (PIC can write EP1 IN buffer)
                                movlw       ENDPT_IN_ONLY
                                movwf       UEP1, ACCESS            ; enable EP1 for interrupt in transfers
    ;; -------- EP1 IN set up, to here ---------

#ifdef SHOW_ENUM_STATUS
                                movlw       0xE0
                                andwf       PORTB, F, ACCESS
                                bsf         PORTB, 3, ACCESS
#endif
                        ends
                        banksel     BD0IBC
                        clrf        BD0IBC, BANKED          ; set byte count to 0
                        movlw       0xC8
                        movwf       BD0IST, BANKED          ; send packet as DATA1, set UOWN bit
                    otherwise
                        bsf         USB_error_flags, 0, BANKED  ; set Request Error flag
                    endi
                    break



4) Get_Status( ENDPOINT ), Set_Feature( ENDPOINT_HALT ), Clear_Feature( ENDPOINT_HALT )
Brad Minch's code handles these requests gracefully, too.
No modification is required.


StandardRequests
            movf        USB_buffer_data+bRequest, W, BANKED
            select
                case GET_STATUS
                    movf        USB_buffer_data+bmRequestType, W, BANKED
                    andlw       0x1F                    ; extract request recipient bits
                    select
                        ...
                        ...
                        case RECIPIENT_ENDPOINT
                            movf        USB_USWSTAT, W, BANKED
                            select
                                case ADDRESS_STATE
                                    movf        USB_buffer_data+wIndex, W, BANKED   ; get EP
                                    andlw       0x0F                                ; strip off direction bit
                                    ifset STATUS, Z, ACCESS                         ; see if it is EP0
                                      ...
                                      ...
                                    otherwise
                                        bsf         USB_error_flags, 0, BANKED      ; set Request Error flag
                                    endi
                                    break
                                case CONFIG_STATE
                                    banksel     BD0IAH
                                    movf        BD0IAH, W, BANKED                   ; put EP0 IN buffer pointer...
                                    movwf       FSR0H, ACCESS
                                    movf        BD0IAL, W, BANKED
                                    movwf       FSR0L, ACCESS                       ; ...into FSR0
                                    movlw       high UEP0                           ; put UEP0 address...
                                    movwf       FSR1H, ACCESS
                                    movlw       low UEP0
                                    movwf       FSR1L, ACCESS                       ; ...into FSR1
                                    movlw       high BD0OST                         ; put BDndST address...
                                    movwf       FSR2H, ACCESS
                                    banksel     USB_buffer_data+wIndex
                                    movf        USB_buffer_data+wIndex, W, BANKED
                                    andlw       0x8F                                ; mask out all but the direction bit
                                                                                    ; and EP number
                                    movwf       FSR2L, ACCESS
                                    rlncf       FSR2L, F, ACCESS
                                    rlncf       FSR2L, F, ACCESS
                                    rlncf       FSR2L, F, ACCESS                    ; FSR2L now contains the proper
                                                                                    ; offset into the BD table
                                                                                    ; for the specified EP
                                    movlw       low BD0OST
                                    addwf       FSR2L, F, ACCESS                    ; ...into FSR2
                                    ifset STATUS, C, ACCESS
                                        incf        FSR2H, F, ACCESS
                                    endi
                                    movf        USB_buffer_data+wIndex, W, BANKED   ; get EP and...
                                    andlw       0x0F                                ; ...strip off direction bit
                                    ifset USB_buffer_data+wIndex, 7, BANKED         ; if the specified EP direction
                                                                                    ; is IN...
                                    andifclr PLUSW1, EPINEN, ACCESS                 ; ...and the specified EP is not
                                                                                    ; enabled for IN transfers...
                                        bsf         USB_error_flags, 0, BANKED      ; ...set Request Error flag
                                    elsifclr USB_buffer_data+wIndex, 7, BANKED      ; otherwise, if the specified EP
                                                                                    ; direction is OUT...
                                    andifclr PLUSW1, EPOUTEN, ACCESS                ; ...and the specified EP is not
                                                                                    ; enabled for OUT transfers...
                                        bsf         USB_error_flags, 0, BANKED      ; ...set Request Error flag
                                    otherwise
                                        movf        INDF2, W                        ; move the contents of the specified
                                                                                    ; BDndST register into WREG
                                        andlw       0x04                            ; extract the BSTALL bit
                                        movwf       INDF0
                                        rrncf       INDF0, F
                                        rrncf       INDF0, F                        ; shift BSTALL bit
                                                                                    ; into the lsb position
                                        clrf        PREINC0                                     
                                        banksel     BD0IBC
                                        movlw       0x02
                                        movwf       BD0IBC, BANKED                  ; set byte count to 2
                                        movlw       0xC8
                                        movwf       BD0IST, BANKED                  ; send packet as DATA1, set UOWN bit
                                    endi
                                    break
                                default
                                    bsf         USB_error_flags, 0, BANKED  ; set Request Error flag
                            ends
                            break
                        default
                            bsf         USB_error_flags, 0, BANKED  ; set Request Error flag
                    ends
                    break
                case CLEAR_FEATURE
                case SET_FEATURE
                    movf        USB_buffer_data+bmRequestType, W, BANKED
                    andlw       0x1F                    ; extract request recipient bits
                    select
                        ...
                        ...
                        case RECIPIENT_ENDPOINT
                            movf        USB_USWSTAT, W, BANKED
                            select
                                case ADDRESS_STATE
                                    movf        USB_buffer_data+wIndex, W, BANKED   ; get EP
                                    andlw       0x0F                                ; strip off direction bit
                                    ifset STATUS, Z, ACCESS                         ; see if it is EP0
                                      ...
                                      ...
                                    otherwise
                                        bsf         USB_error_flags, 0, BANKED      ; set Request Error flag
                                    endi
                                    break
                                case CONFIG_STATE
                                    movlw       high UEP0                           ; put UEP0 address...
                                    movwf       FSR0H, ACCESS
                                    movlw       low UEP0
                                    movwf       FSR0L, ACCESS                       ; ...into FSR0
                                    movlw       high BD0OST                         ; put BD0OST address...
                                    movwf       FSR1H, ACCESS
                                    movlw       low BD0OST
                                    movwf       FSR1L, ACCESS                       ; ...into FSR1
                                    movf        USB_buffer_data+wIndex, W, BANKED   ; get EP
                                    andlw       0x0F                                ; strip off direction bit
                                    ifclr STATUS, Z, ACCESS                         ; if it was not EP0...
                                        addwf       FSR0L, F, ACCESS                    ; add EP number to FSR0
                                        ifset       STATUS, C, ACCESS
                                            incf        FSR0H, F, ACCESS
                                        endi
                                        rlncf       USB_buffer_data+wIndex, F, BANKED
                                        rlncf       USB_buffer_data+wIndex, F, BANKED
                                        rlncf       USB_buffer_data+wIndex, W, BANKED   ; WREG now contains the proper
                                                                                        ; offset into the BD table
                                                                                        ; for the specified EP
                                        andlw       0x7C                                ; mask out all but the direction
                                                                                        ; bit and EP number (after three
                                                                                        ; left rotates)
                                        addwf       FSR1L, F, ACCESS                    ; add BD table offset to FSR1
                                        ifset       STATUS, C, ACCESS
                                            incf        FSR1H, F, ACCESS
                                        endi
                                        ifset USB_buffer_data+wIndex, 1, BANKED         ; if the specified EP direction
                                                                                        ; (now bit 1) is IN...
                                            ifset INDF0, EPINEN, ACCESS                 ; if the specified EP is enabled
                                                                                        ; for IN transfers...
                                                ifl USB_buffer_data+bRequest, ==, CLEAR_FEATURE
                                                     clrf        INDF1                   ; clear the stall on the
                                                                                        ; specified EP
                                                otherwise
                                                    movlw       0x84
                                                    movwf       INDF1                   ; stall the specified EP
                                                endi
                                            otherwise
                                                bsf         USB_error_flags, 0, BANKED      ; set Request Error flag
                                            endi
                                        otherwise                                       ; ...otherwise the specified EP
                                                                                        ; direction is OUT, so...
                                            ifset INDF0, EPOUTEN, ACCESS                ; if the specified EP is
                                                                                        ; enabled for OUT transfers...
                                                ifl USB_buffer_data+bRequest, ==, CLEAR_FEATURE
                                                    movlw       0x88
                                                    movwf       INDF1                   ; clear the stall on the
                                                                                        ; specified EP                                                   
                                                otherwise
                                                    movlw       0x84
                                                    movwf       INDF1                   ; stall the specified EP
                                                endi
                                            otherwise
                                                bsf         USB_error_flags, 0, BANKED      ; set Request Error flag
                                            endi
                                        endi
                                    endi
                                    ifclr USB_error_flags, 0, BANKED    ; if there was no Request Error...
                                        banksel     BD0IBC
                                        clrf        BD0IBC, BANKED          ; set byte count to 0
                                        movlw       0xC8
                                        movwf       BD0IST, BANKED          ; send packet as DATA1, set UOWN bit
                                    endi
                                    break
                                default
                                    bsf         USB_error_flags, 0, BANKED  ; set Request Error flag
                            ends
                            break
                        default
                            bsf         USB_error_flags, 0, BANKED  ; set Request Error flag
                    ends
                    break



5) Run EPs
Brad Minch's code implements polling-style handling to run EP.
- UOWN bit on BDnSTAT (BD1IST) is polled to detect the completion of transaction
- DTS (data toggle) is inverted just before passing ownership to SIE

This code writes on IN EP.
For OUT EP, the code is almost same, except for reading out data from the buffer, on transaction completion.


APPLICATION code
Main
            ...
            ...
            repeat
                banksel     BD1IST
                ifclr BD1IST, UOWN, BANKED          ; check to see if the PIC owns the EP1 IN buffer
                    ...
                    ...
                    call        SendKeyBuffer
                endi
                call        ServiceUSB          ; service USB requests
            forever



SendKeyBuffer
    ;; -------- EP1 IN buffer handling ---------
            banksel     BD1IAH
            movf        BD1IAH, W, BANKED       ; put EP1 IN buffer pointer...
            movwf       FSR0H, ACCESS
            movf        BD1IAL, W, BANKED
            movwf       FSR0L, ACCESS           ; ...into FSR0
            banksel     Key_buffer
            movf        Key_buffer, W, BANKED   ; copy modifier byte...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+1, W, BANKED ; copy reserved byte...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+2, W, BANKED ; copy keycode 1...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+3, W, BANKED ; copy keycode 2...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+4, W, BANKED ; copy keycode 3...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+5, W, BANKED ; copy keycode 4...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+6, W, BANKED ; copy keycode 5...
            movwf       POSTINC0                ; ...to EP1 IN buffer
            movf        Key_buffer+7, W, BANKED ; copy keycode 6...
            movwf       INDF0                   ; ...to EP1 IN buffer

    ;; -------- EP1 IN BD handling, from here ---------
            banksel     BD1IBC
            movlw       0x08
            movwf       BD1IBC, BANKED          ; set EP1 IN buffer byte count to 8
            movf        BD1IST, W, BANKED       ; get EP1 IN status register
            andlw       0x40                    ; extract the DATA01 bit (DTS bit)  <-------------- data toggle
            xorlw       0x40                    ; toggle the DATA01 bit
            iorlw       0x88                    ; set UOWN and DTSEN bits
            movwf       BD1IST, BANKED          ; send packet
    ;; -------- EP1 IN BD handling, to here ---------
            return



Tsuneo
#2
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/03 07:52:44 (permalink)
0
Tsuneo,
 
That is quite extraordinarily helpful - thank you very much indeed.
 
I will now go off and think about this very hard - I'll let you know how it goes.
 
Thanks again,
 
Martin
#3
newfound
Super Member
  • Total Posts : 1827
  • Reward points : 0
  • Joined: 2003/11/07 12:35:49
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/03 10:02:45 (permalink)
+2 (1)
Following on from Chinzei's excellent effort here are some working examples. Note that this code is a mix of Brad's, Microchip's and my own. It has been modified for the 18F14K50 but that is not an issue as the differences are handled in the header files etc and the changes to the code only create surpurfluous but harmless Bank switching. This code works but is "retired" and I have since rewritten more complete versions. Please excuse any untidiness in my comments as I just quickly added them and hope they are accurate. I also hope that it serves as a basic working model to demonstate a generic BULK USB device.

First, here are working descriptors for bulk IN and OUT on EP1. Note that There are no string descriptors defined or required as I need the code to be a small as possible. The defines for the constants are the same as in Brad's existing code.


Descriptor_begin
Device
                      db                      0x12, DEVICE                    ; bLength, bDescriptorType
                      db                      0x00, 0x02                      ; bcdUSB (low byte), bcdUSB (high byte)
                      db                      0x00, 0x00                      ; bDeviceClass, bDeviceSubClass
                      db                      0x00, EP0_MAX_PACKET_SIZE       ; bDeviceProtocl, bMaxPacketSize
                      db                      0xD8, 0x04                      ; idVendor (low byte), idVendor (high byte)
                      db                      0x0B, 0x00                      ; idProduct (low byte), idProduct (high byte)
                      db                      0x00, 0x00                      ; bcdDevice (low byte), bcdDevice (high byte)
                      db                      0x00, 0x00                      ; iManufacturer string, iProduct string (STRING indexes go on this line)
                      db                      0x00, NUM_CONFIGURATIONS        ; iSerialNumber string(none), bNumConfigurations

Configuration1
                      db                      0x09, CONFIGURATION             ; bLength, bDescriptorType
                      db                      0x20, 0x00                      ; wTotalLength (low byte), wTotalLength (high byte)
                      db                      NUM_INTERFACES, 0x01            ; bNumInterfaces, bConfigurationValue
                      db                      0x00, 0x80                      ; iConfiguration (none), bmAttributes
                      db                      0x32, 0x09                      ; bMaxPower (100 mA), bLength (Interface1 descriptor starts here)
                      db                      INTERFACE, 0x00                 ; bDescriptorType, bInterfaceNumber
                      db                      0x00, 0x02                      ; bAlternateSetting, bNumEndpoints (excluding EP0)
                      db                      0x00, 0x00                      ; bInterfaceClass, bInterfaceSubClass
                      db                      0x00, 0x00                      ; bInterfaceProtocol, iInterface (none)
EndPointdsc
                      db                      0x07, ENDPOINT                  ; bLength, bDescriptorType
                      db                      0x01, 0x02                      ; bEndpointAddress, bmAttributes (BULK)
                      db                      0x40, 0x00                      ; wMaxPacketSizeL, wMaxPacketSizeH
                      db                      0x00, 0x07                      ; bInterval, bLength
                      db                      ENDPOINT, 0x81                  ; bDescriptorType, bEndpointAddress       
                      db                      0x02, 0x40                      ; bmAttributes (BULK), wMaxPacketSizeL
                      db                      0x00, 0x00                      ; wMaxPacketSizeH, bInterval


I removed all EP1 handling from Brad's code except for the CLEAR ENDPOINT stuff on a bus reset and on configuration. Instead I enabled EP1 after finding that the device was in a configured state.


WaitConfigLoop
                      rcall ServiceUSB                        ; service USB requests...
                      movf USB_USWSTAT, W, my_a_b 
                      sublw CONFIG_STATE
                      bnz WaitConfigLoop

                      movlw   0
                      movwf  trf_state
                      rcall BootUSBInit


Main loop goes here....


        repeat
           rcall ServiceUSB         ; service USB requests...
           rcall BootService
        forever


The reason why I did this was to make the code more universal and easier to modify for other purposes. I simply use "Brad's code" for EP0 control transfers.

Here is where EP1 is fully initialized. This code is from Microchip's FS USB generic bootloader. I simply cut and paste it from the disassembly listing.


BootUSBInit
              clrf            trf_state          ;EP1 state machine control
              banksel         UEP1            ; Different bank than usual for 18F14k50
              movlw           0x1e            ; EP1 set-up; Enable IN/OUT, no control, Hshk (toggle) enabled
              movwf           UEP1, BANKED
              banksel         BD0OST
              movlw           0x40            ; ep max count
              movwf           BD1OBC, BANKED
              movlw           Low (BootBuff)  ; EP1 OUT gets a buffer...
              movwf           BD1OAL, BANKED
              movwf           BD1IAL, BANKED  ; EP1 IN gets a buffer.
              movlw           high (BootBuff) ; Note that IN and OUT buffers are the same!
              movwf           BD1OAH, BANKED
              movwf           BD1IAH, BANKED
              movlw           0x88            ; Prime DTS toggled to "0" on first transfer
              movwf           BD1OST, BANKED
              movlw           0x40
              movwf           BD1IST, BANKED
              return


After that the BootService is called in a loop after the usual ServiceUSB. Note that the code below is a command - response type arrangement. As such it can share the same buffer for both EP1 IN and OUT transfers. I suggest you give separate buffer space for each to allow for more flexible arrangements.

The following code is mostly a reinterpretation of Microchip FS USB generic code. I am sure that the simple State Machine (SM) (trf_state) is self explanatory.


BootService
; Check that device is configured and not in suspend mode.
              movlw           CONFIGURED_STATE
              subwf           usb_device_state, W
              bnz             EP1_Nothingv
              movf            UCON, W
              andlw           0x2
              bnz             EP1_Nothingv

; If SM == 0 then we can go check for OUT transfer on EP1
              decf            trf_state, W
              bnz             BootCheckCMD

; Else SM == 1 we may have a IN transfer happening, If so leave until completed

              btfsc           BD1IST, 0x7, BANKED
              bra             EP1_Nothing   

; Arrive here when there are no active transfers
; Configure OUT buffer so the host can send
              movlw           0x40
              movwf           BD1OBC, BANKED
              andwf           BD1OST, F, BANKED
              btg             BD1OST, 0x6, BANKED
              movlw           0x88
              iorwf           BD1OST, F, BANKED
              clrf            trf_state
EP1_Nothingv         
              bra             EP1_Nothing

; An OUT transfer may be waiting, leave if none
BootCheckCMD
              btfsc           BD1OST, 0x7, BANKED
              bra             EP1_Nothing

; Arrive here after we have a OUT transfer waiting for us.
; Handle your OUT transfer data as required




; IN transfer handler below. Finished with OUT transfers.

; Left this stub to show how bootloader uses EP1 IN transfers
ReadProgMemDone
              movlw           0x5  ; Byte count for IN transfer
              addwf           BootCounter, W
              bra             USB_Ready_In

USB_Ready_In1     ;When I only want to send one byte
              movlw           0x1       ; Count = One byte (status etc..)
USB_Ready_In
              bsf             trf_state, 0
              movf            WREG
              bz              EP1_Nothing

; Here we are starting an BULK IN transfer on EP1
              movwf           BD1IBC, BANKED    ;Was  bootcounter
              movf            BD1IST, W, BANKED         
              andlw           0x40
              btg             WREG, 0x6
              iorlw           0x88
              movwf           BD1IST, BANKED
EP1_Nothing
              return  0
              end



That's it. ALL handling of EP1 from configuration to usage is covered by the above code. That is it is a complete working wrapper and you can modify as required. As I said it is a mix of Brad's, Microchip's and my own code and can do with more work. For my purposes it worked so I left good enough alone.

Hope it helps you and/or others.

EDIT: added main loop code so the call to BootInit didn't appear to fall through to same code.

EDIT 2 Added a missing BANKED derivative, not that it would matter as the compiler "knows" where the SFR is. Also note that in my scheme the BSR is permanently pointed at the Buffer descriptor page and all other variables are in the access bank.
post edited by newfound - 2010/01/03 20:20:46
#4
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/04 08:40:18 (permalink)
0
Thank you, "newfound", for your help.  Your method of "cleaning
out" the EP1 stuff from Brad's code and putting it somewhere else
is beginning to look quite attractive.
 
Since I got Chinzei's help I have spent some more hours on this
code and I have made some progress.  I now have both usb_bulk_write
and usb_bulk_read working, once.  I have made no effort at all to
change the DATA0/1 value, so I am not surprised they only work once.
I am getting closer to success, though I'm not there yet.  For
the benefit of others, here are some of the pits I fell into.
 
In the Set_Configuration section I had to set up EP1_OUT as well.
I have done this at the exact point where Chinzei had the line
"    ;; -------- EP1 IN set up, from here ---------"
as follows:
 
 banksel BD1OAL
 movlw low (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
 movwf BD1OAL, BANKED
 movlw high (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
 movwf BD1OAH, BANKED
 movlw MAX_PACKET_SIZE  ; my MAX_PACKET_SIZE = 64
 movwf BD1OBC, BANKED
 
; Set UOWN bit (USB can write)
 
 movlw 0xC8  ; I am not sure about this value ... 
 movwf BD1OST, BANKED
 
In the setup of EP_IN Chinzei had:
 
        movlw       ENDPT_IN_ONLY
        movwf       UEP1, ACCESS            ; enable EP1 for interrupt in transfers
 
I need both EP1_IN *and* EP1_OUT to be enabled, so I have defined a new constant
 
#define ENDPT_NON_CONTROL 0x1E ; enable for in, and out
 
and replaced ENDPT_IN_ONLY with ENDPT_NON_CONTROL
 
As I have put the definition of EP1_IN after EP1_OUT, its buffer starts
at USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE+MAX_PACKET_SIZE
 
A bulk "transaction" seems to comprise a setup, data and handshake (ACK)
package.  I am assuming that the PIC18F4550's SIE deals with all
this and I see this as a single block, and I will get a TRNIF
interrupt flag when it is all over.  I wasn't sure where a
"transaction" started and stopped.
 
I was a bit unsure of the correct value for the Byte count "BC"
register for all endpoints, IN and OUT, EP0 and EP1.  I have
been using 64 all round and it seems to be OK.
 
When I want to set up an IN transaction, I have to fill the
buffer with whatever data I want transferred, and then I have
to set UOWN so the SIE has control.  I was missing that because
at this early stage I was  not transferring any "real" data,
so had not purposefully loaded up the buffers and set UOWN.
 
In Brad's code, DATA0/1 is set up in different places.
I can see action in routine StandardRequests,
CLEAR/SET FEATURE | RECIPIENT_ENDPOINT | CONFIG_STATE
(approx line 743 of the lab2.asm code) where I see
BDnOST set to 0x88 (CLEAR_FEATURE) or 0x84 (otherwise), and
BDnIST set to 0x00 (CLEAR_FEATURE) or 0x84 (otherwise).
 
However, I also see things happening around line 951, still
in routine StandardRequests,
SET_CONFIGURATION when USB_curr_config = CONFIG_STATE when
BD1IST is set to 0x48.
 
Then, in SendKeyBuffer, (line 1262), BD1IST is set to 0xC8
or 0x88 and DATA0/1 is toggled.
 
I am still unsure why these registers are changed so often.
 
I now have to set up my top level code so that it deals with
DATA0/1 correctly and eventually, hopefully, I will be able to
send more than 64 bytes in one ms frame, which was the
major aim all along.
 
Then I'm going to try isochronous transfer and the undocumented
mysteries of libusb-win32.
 
Again, my thanks to Chinzei and "newfound" for their extremely
useful help and encouragement.
 
Martin
 
 
 
#5
newfound
Super Member
  • Total Posts : 1827
  • Reward points : 0
  • Joined: 2003/11/07 12:35:49
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/04 10:25:46 (permalink)
0
> Thank you, "newfound", for your help.  Your method of "cleaning
> out" the EP1 stuff from Brad's code and putting it somewhere else
> is beginning to look quite attractive.

In the long run you will find it very useful. I have broken down Brad's code to a basic "controlbox" and now I can quickly layer anything over it. I have a bootloader, HID, CDC layers that are separate modules and I simply link the .O files together. The code is now highly reuseable.

>
> Since I got Chinzei's help I have spent some more hours on this
> code and I have made some progress.  I now have both usb_bulk_write
> and usb_bulk_read working, once.  I have made no effort at all to
> change the DATA0/1 value, so I am not surprised they only work once.

It is easy. You need to do this on all your EP1 transfers. There is a way to eliminate the requirement for data toggling but really you probably are better off getting it to work.


                        movlw       0x40
                        xorwf       BD1OST, W, BANKED            ; toggle the DATA01 bit
                        andlw       0x40                 ; clear the PIDs bits
                        iorlw       0x88                 ; set UOWN and DTS bits
                        movwf       BD1OST, BANKED


Do the same on EP1 IN transfers as well. Just change BD1OST to BD1IST

> In the Set_Configuration section I had to set up EP1_OUT as well.
> I have done this at the exact point where Chinzei had the line
> "    ;; -------- EP1 IN set up, from here ---------"
> as follows:
>
> banksel BD1OAL
> movlw low (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
> movwf BD1OAL, BANKED
> movlw high (USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE)
> movwf BD1OAH, BANKED
> movlw MAX_PACKET_SIZE  ; my MAX_PACKET_SIZE = 64
> movwf BD1OBC, BANKED

Ok, really you need to have a different MAX-PACKET_SIZE for EP1. In your include file have these two lines:


#define EP0_MAX_PACKET_SIZE 0x08
#define EP1_MAX_PACKET_SIZE 0x40


Generally the MAX_PACKET_SIZE for EP0 is 0x08. There is rarely any advantage in changing this.

The following descriptors use the above defines to set EP0 EP0_MAX_PACKET_SIZE = 0x08 and EP1_MAX_PACKET_SIZE = 0x40
(remember Brad's code is hard coded for EP0_MAX_PACKET_SIZE = 8) (Unless he changed it in a later revision.)

Actually, his code HID keyboard code also had EP1_MAX_PACKET_SIZE of 0x08 hard coded in places too. (Bad Brad, Bad!)



; Descriptors for generic BULK IN/OUT on EP1, Control transfers on EP0.
; EP0 MAX_PACKET_SIZE = 0x08, EP1_MAX_PACKET_SIZE = 0x40
; No string descriptors.

Descriptor_begin
Device
                     db                      0x12, DEVICE                    ; bLength, bDescriptorType
                     db                      0x00, 0x02                      ; bcdUSB (low byte), bcdUSB (high byte)
                     db                      0x00, 0x00                      ; bDeviceClass, bDeviceSubClass
                     db                      0x00, EP0_MAX_PACKET_SIZE       ; bDeviceProtocl, bMaxPacketSize
                     db                      0xD8, 0x04                      ; idVendor (low byte), idVendor (high byte)
                     db                      0x0B, 0x00                      ; idProduct (low byte), idProduct (high byte)
                     db                      0x00, 0x00                      ; bcdDevice (low byte), bcdDevice (high byte)
                     db                      0x00, 0x00                      ; iManufacturer string, iProduct string (STRING indexes go on this line)
                     db                      0x00, NUM_CONFIGURATIONS        ; iSerialNumber string(none), bNumConfigurations

Configuration1
                     db                      0x09, CONFIGURATION             ; bLength, bDescriptorType
                     db                      0x20, 0x00                      ; wTotalLength (low byte), wTotalLength (high byte)
                     db                      NUM_INTERFACES, 0x01            ; bNumInterfaces, bConfigurationValue
                     db                      0x00, 0x80                      ; iConfiguration (none), bmAttributes
                     db                      0x32, 0x09                      ; bMaxPower (100 mA), bLength (Interface1 descriptor starts here)
                     db                      INTERFACE, 0x00                 ; bDescriptorType, bInterfaceNumber
                     db                      0x00, 0x02                      ; bAlternateSetting, bNumEndpoints (excluding EP0)
                     db                      0x00, 0x00                      ; bInterfaceClass, bInterfaceSubClass
                     db                      0x00, 0x00                      ; bInterfaceProtocol, iInterface (none)
EndPointdsc
                     db                      0x07, ENDPOINT                  ; bLength, bDescriptorType
                     db                      0x01, 0x02                      ; bEndpointAddress, bmAttributes (BULK)
                     db                      EP1_MAX_PACKET_SIZE, 0x00       ; wMaxPacketSizeL, wMaxPacketSizeH
                     db                      0x00, 0x07                      ; bInterval, bLength
                     db                      ENDPOINT, 0x81                  ; bDescriptorType, bEndpointAddress        
                     db                      0x02, EP1_MAX_PACKET_SIZE       ; bmAttributes (BULK), wMaxPacketSizeL
                     db                      0x00, 0x00                      ; wMaxPacketSizeH, bInterval

Change the EP1 OUT buffer code to:

     banksel BD1OAL
     movlw low (USB_Buffer + EP0_MAX_PACKET_SIZE + EP0_MAX_PACKET_SIZE)
     movwf BD1OAL, BANKED
     movlw high (USB_Buffer + EP0_MAX_PACKET_SIZE + EP0_MAX_PACKET_SIZE)
     movwf BD1OAH, BANKED
     movlw EP1_MAX_PACKET_SIZE  ; my MAX_PACKET_SIZE = 64
     movwf BD1OBC, BANKED

(I added spaces around the "+" as it is much easier for me to read.)


> ; Set UOWN bit (USB can write)
>
> movlw 0xC8  ; I am not sure about this value ...

It is covered in the data sheet. UOWN = SIE, DATA0/1 = DATA1, HSHK = enabled.

> movwf BD1OST, BANKED
>
> In the setup of EP_IN Chinzei had:
>
>        movlw       ENDPT_IN_ONLY
>        movwf       UEP1, ACCESS            ; enable EP1 for interrupt in transfers

Actually, enable it for non control transfers. Not just interrupt.

>
> I need both EP1_IN *and* EP1_OUT to be enabled, so I have defined a new constant
>
> #define ENDPT_NON_CONTROL 0x1E ; enable for in, and out

This is already defined in the header file.

>
> and replaced ENDPT_IN_ONLY with ENDPT_NON_CONTROL

Right. 0x1E it is.
>
> As I have put the definition of EP1_IN after EP1_OUT, its buffer starts
> at USB_Buffer+MAX_PACKET_SIZE+MAX_PACKET_SIZE+MAX_PACKET_SIZE

Use: USB_Buffer + EP0_MAX_PACKET_SIZE + EP0_MAX_PACKET_SIZE + EP1_MAX_PACKET_SIZE

>
> A bulk "transaction" seems to comprise a setup, data and handshake (ACK)
> package.  I am assuming that the PIC18F4550's SIE deals with all
> this and I see this as a single block, and I will get a TRNIF
> interrupt flag when it is all over.  I wasn't sure where a
> "transaction" started and stopped.

Correct. You can use this or just poll the state of the UOWN bit as per the Microchip framework I posted eariler.

>
> I was a bit unsure of the correct value for the Byte count "BC"
> register for all endpoints, IN and OUT, EP0 and EP1.  I have
> been using 64 all round and it seems to be OK.

No, it may work but it is dangerous. Last time I looked brad's code had hardcoded the EP0 end point size as 0x08. USB can be fault tolerant but don't count on it. I spent a lot of time experimenting with EP0_MAX_PACKET_SIZE = 0x40 but I found no advantage. Use 0x08 and save on the RAM buffer space and don't alter Brad's code for EP0 control transfers.  

>
> When I want to set up an IN transaction, I have to fill the
> buffer with whatever data I want transferred, and then I have
> to set UOWN so the SIE has control.  I was missing that because
> at this early stage I was  not transferring any "real" data,
> so had not purposefully loaded up the buffers and set UOWN.

Set UOWN and toggle DTS bit as outlined above. If you get your first packet then you know you have "primed" the DTS bit correctly and every further transfer you simply toggle it. It is that easy.

>
> In Brad's code, DATA0/1 is set up in different places.

See the link below. Believe me, for EP1 the DATA0/1 toggling is very straight forward. For EP0 it can be complicated. However generally incorrect DATA0/1 toggles on EP0 are ignored, certainly when handling SETUP packets. Other CONTROL transfers need to get it right.  

> I can see action in routine StandardRequests,
> CLEAR/SET FEATURE | RECIPIENT_ENDPOINT | CONFIG_STATE
> (approx line 743 of the lab2.asm code) where I see
> BDnOST set to 0x88 (CLEAR_FEATURE) or 0x84 (otherwise), and

> BDnIST set to 0x00 (CLEAR_FEATURE) or 0x84 (otherwise).
 
Clear STALL on end point (otherwise (SET_FEATURE) set STALL on EP

See http://www.usbmadesimple.co.uk/ page 4 for details on what is going on here.

> However, I also see things happening around line 951, still
> in routine StandardRequests,
> SET_CONFIGURATION when USB_curr_config = CONFIG_STATE when
> BD1IST is set to 0x48.
>
> Then, in SendKeyBuffer, (line 1262), BD1IST is set to 0xC8
> or 0x88 and DATA0/1 is toggled.

Then, in SendKeyBuffer, (line 1262), BD1IST is set to 0xC8  or 0x88 _AS_ DATA0/1 is toggled.



>
> I am still unsure why these registers are changed so often.

UOWN, DTS, STALL bits change. The stall bit is only changed on EP0 in case of an error or in the case of a SET_FEATURE request. UOWN and DTS are not hard to follow. 

>
> I now have to set up my top level code so that it deals with
> DATA0/1 correctly and eventually, hopefully, I will be able to
> send more than 64 bytes in one ms frame, which was the
> major aim all along.

You are close, just back track a little and fix the end point sizes and add the EP1 DATA0/1 toggling. Read up at http://www.usbmadesimple.co.uk as it does make things a lot clearer that say the USB specification. Also go to http://www.beyondlogic.org/usbnutshell/usb1.htm for enother good reference.

>
> Then I'm going to try isochronous transfer and the undocumented
> mysteries of libusb-win32.

Great, I may have to ask for your help on the PC side of things one day.
>
> Again, my thanks to Chinzei and "newfound" for their extremely
> useful help and encouragement.
>
> Martin

Good luck!

Jim
#6
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/04 13:45:41 (permalink)
0
Jim,
 
Thanks for your help - again!
 
The background for all this may be slightly relevant.  For the last twenty years, or so, I have worked in analogue electronics, either on a PCB or on an IC, in industry and at university.  A couple of years ago I made up a new student lab that involved an analogue IC and I decided that I would use a PIC to control the stuff that controls it (ADC, DAC, DDS, etc).
 
I didn't know much about microcontrollers, so it was time to learn and, though I say it myself, I've just about got most of it.
 
I took 512 measurements from the analogue IC and I wanted them transferred to a PC.  I used control transfers for that with a 64-byte packet size, and it happened in the blink of a (human) eye.
 
I then decided to rebuild another lab which involved recording speech. That gave me 512 kBytes of data in an external RAM, and I transfered that using control transfers too, though it took quite a lot of seconds to do it.  However, it works, so I have pretty- well thrashed the control transfers.  I would like my code to be backward-compatible, so I would rather keep the control transfers at 64 bytes.  I don't think it does much harm, and I can afford the buffer space.
 
Another requirement has come along that needs me to transfer 256 bytes in a single milli-second, and I can't do that with a full-speed control transfer. Hence the move to bulk transfers and, maybe later, isochronous transfers.
 
With much of this PIC stuff I have no trouble at all knowing *how* to do some small thing (like toggling DATA0/1, which I would do with "btg BD1OST, 6"), but I have trouble with *when* and *why*.
 
I have single 64-byte transfers working, but I am now considering going for a 256-byte transfer.  That should be spilt into four 64-byte transfers and I sincerely hope I can get more than one of these in a ms frame, or I've been wasting my time.
 
I will issue a single command like
ret = usb_bulk_read(dev, EP_IN, tmp, sizeof(tmp), 1000);
and I assume that the PC will split that into four transactions behind the scenes.
 
So, how do I prepare the PIC for this?  I thought I would use a control transfer to send two bytes which, multiplied by 256, would be the starting address in SRAM of the block I want.  While grabbing those two bytes I could also take the first 64 bytes of the 256-byte data block and put then into the EP1_IN buffer and set EP1_IN(UOWN).  I would assume that DATA0/1 would be correct from the last transaction.
 
When the bulk read request comes in I will watch EP1_IN(UOWN) or the TRNIF flag and when it becomes 0 I will fill the buffer with the next 64 bytes, toggle DATA0/1, and then set EP1_IN(UOWN) again.  I will repeat this four times and then quit, knowing that the usb_read_buffer command will have asked for 256 bytes.
 
I don't like this as it depends on me knowing that the request will be for 256 bytes. But how could I get the PIC to recognise a "last" transaction?
 
Or could I just assume that I was going to start at memory 0x0000 and transfer 512kBytes in one bulk transfer call?  But then how would the PIC recognise the start of the transfer?  Would I just ensure it was always ready?
 
The more I think about this level of the coding, the more I think that your way of pulling all this stuff out of Brad's code and re-making it at another level would be a good plan.
 
You said " remove *all* EP1 handling from Brad's code except for the CLEAR ENDPOINT stuff on a bus reset and on configuration".  I assume that means leaving the
 clrf UEP1, ACCESS
 clrf UEP2, ACCESS
 .
 .
 clrf UEP15, ACCESS
stuff but removing all else to do with all endpoints except EP0?
 
I have to admit that changing the code so drastically would be a move away from something that works, and that's doesn't make me feel happy right now.  Still, it may be a bullet that needs bitten.
 
If you have any thoughts on this they would be much appreciated.
 
Martin

#7
chinzei
Super Member
  • Total Posts : 2250
  • Reward points : 0
  • Joined: 2003/11/07 12:39:02
  • Location: Tokyo, Japan
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/04 16:28:47 (permalink)
0
I have single 64-byte transfers working, but I am now considering going for a 256-byte transfer.  That should be spilt into four 64-byte transfers and I sincerely hope I can get more than one of these in a ms frame, or I've been wasting my time.

As of the USB side, 256 bytes (4x full-size bulk packets) on single USB frame is an ordinary job.
As you are writing in Asm, the low core performance of PIC18F is not so much problem.
Rather, the transfer speed between the external memory and PIC may be the bottle neck.

I don't like this as it depends on me knowing that the request will be for 256 bytes. But how could I get the PIC to recognise a "last" transaction?

Or could I just assume that I was going to start at memory 0x0000 and transfer 512kBytes in one bulk transfer call?  But then how would the PIC recognise the start of the transfer?  Would I just ensure it was always ready?

When PC app requests as many bytes in single transfer as possible, you'll get much better speed performance.
It's the simplest, but most effective way to speed up bulk transfer.
When you split the transfer into shorter ones, each call takes 1 - 3 ms overhead, to synchronize to the USB bus frame. Greater transfer size reduces these overhead.

Usually, PC device driver accepts 512K Bytes request in single transfer call, but it depends on the device driver.
Up to 4K Bytes (page memory size of PCI bus on PC), PC hardware host controller splits the transfer into 64-bytes transactions. For greater size, PC device driver splits the request into 4K-bytes chunks first, before passing it to the host controller. But some device driver doesn't implement this mechanism well.
It's better to confirm the max size handled by the device driver.


Your transfer procedure is implemented as follows.
Firstly, PC app sends a command to the device over bulk OUT endpoint, or vendor write request (control write transfer).
In this command, start address and trasfer size are notified. And then, PC requests the transfer over bulk IN EP in single call.
On the device side, the reception of the command starts the data transfer over the bulk IN EP, packet by packet.
When it reaches to the requested size, the device stops the transfer.

You said " remove *all* EP1 handling from Brad's code except for the CLEAR ENDPOINT stuff on a bus reset and on configuration".  I assume that means leaving the
clrf UEP1, ACCESS
clrf UEP2, ACCESS
.
.
clrf UEP15, ACCESS
stuff but removing all else to do with all endpoints except EP0?

I have to admit that changing the code so drastically would be a move away from something that works, and that's doesn't make me feel happy right now.  Still, it may be a bullet that needs bitten.

The code is there for USB spec compliance.
It doesn't work so much in daily usage, but when you get USB logo for your products, it works. wink
Or, the code may guard your device from bad-mannered embedded hosts.
Anyway, it does not do any harm, except for occupying code space. Leave it there.
I recommend you to concentrate in your current task - speedy bulk transfer. wink

Tsuneo
#8
newfound
Super Member
  • Total Posts : 1827
  • Reward points : 0
  • Joined: 2003/11/07 12:35:49
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/04 16:59:59 (permalink)
0


I had just written a long reply to your questions and on review I became somewhat confused about what you were confused about. Then I sore Chinzei was online and I hoped that he might reply and give me  another vantage point to address your questions.

Well as it turns out the advice given is exactly the same as mine.  Part of my long post:


So, how do I prepare the PIC for this?  I thought I would use a control transfer to send two bytes which, multiplied by 256, would be the starting address in SRAM of the block I want.  While grabbing those two bytes I could also take the first 64 bytes of the 256-byte data block and put then into the EP1_IN buffer and set EP1_IN(UOWN).  I would assume that DATA0/1 would be correct from the last transaction.


You lost me here. How could you do that if you are waiting for the address of the block to send to the host. Now if you are specifying an address why not a byte count as well and a command byte. Some of your questions below would be addressed then.
....

See what is in common? A command, address and transfer count. The USB layer will not do this for you. You sort this out between the HOST APP and your own firmware each side of the USB "pipe."

Sure you could just assume things about the transfer but you said you didn't like that and nether do I so I will leave this part of the reply out.

Further more from abandon reply....

USB is not going to tell your firmware "hey send the bytes I want now." (It will only look to see if there are bytes ready for an IN transfer.) This is a function of the higher level layers on the host and your firmware code negotiating what is to be sent and when. You can do this via an OUT transfer with some data for your firmware to interpret (command, address and byte count).  A good example can be found in the microchip FS USB generic bootloader code. This is doing close to what you want. I strongly recommend that you look at it and other microchip framework code.

and again....

I say that you send from the host a command, count and address via a BULK OUT or control transfer. This is pretty standard and there are lots of examples to follow.  It is flexible and upgradable. Other than the simple implied idea above you are going to have to do this as, again, USB does not provide this level of protocol support.

Now you have the same advice from two people.


The more I think about this level of the coding, the more I think that your way of pulling all this stuff out of Brad's code and re-making it at another level would be a good plan.


I believe so. One possible advantage is that it would allow easier use of ping-pong buffering on end points other than EP0. (Provided your silicon revision allow for it, check the errata sheet for details.) You may not need ping-pong buffering but it is an option.

It also makes for easier code development as you can separate the source files and not stuff up already working control transfer code. If your device is enumerating fully then don't break it. Troubleshooting USB can be difficult especially if you do not have the fancy tool$.


You said " remove *all* EP1 handling from Brad's code except for the CLEAR ENDPOINT stuff on a bus reset and on configuration".  I assume that means leaving the
clrf UEP1, ACCESS
clrf UEP2, ACCESS
.
.
clrf UEP15, ACCESS
stuff but removing all else to do with all endpoints except EP0?


Yes, leave that, it makes for a robust system.   In my code when there was a TRF interrupt I checked the PID bits and if it was not for EP0 I simply cleared the flag and returned. The transfers on other end points are done by watching the UOWN bits in BDxxST.


I have to admit that changing the code so drastically would be a move away from something that works, and that's doesn't make me feel happy right now.  Still, it may be a bullet that needs bitten.

If you have any thoughts on this they would be much appreciated.

Martin


Bit hard! It is a worthwhile exercise and fun. Get some good understanding and a framework you can work with. Later you will be looking around for other things to do now that you are a master of USB. Who knows, you might even make a dollar out of it.

#9
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/07 15:37:43 (permalink)
0
Thanks to the help I have received on this forum, for which grateful
thanks, I seem to have cracked the Bulk Transfer scheme.
I went more for "newfound"'s scheme and removed all the EP1 handling
from my version of Brad's code.  However, I must admit I didn't see
how the FSM worked and so I went for a different scheme there.  I may
find the disadvantage of that later!
Some of what I have done is appended.  Unfortunately I fear that it may
make a lot more sense to me than it will to a casual reader.  Also it
will be clear that I am no programmer and that this code could
probably be written *much* better.
However, maybe this will be able to give some poor soul a way forward
when he/she is completely stuck, as I was some days ago.
All the best.
Martin

My "Main" code decides what to do based on user input and one choice is to
run the USB, so there is a file Usb.asm that is the effective "Main" for
this section.  It initialises the USB, etc, etc, and it eventually gets to
 
CODE START *********************
Usb_05
 call ServiceUSB  ; service USB requests
 tstfsz UEP1   ; skip if UEP1 = 0, so EP1 not initialised
 bra Usb_06
 call Ep1Init
Usb_06
 call Ep1Service
 bra Usb_05   ; Go round again
CODE STOP ***********************
 
Ep1Init puts the right addresses and byte-count values in the EP1_IN and OUT
buffer descriptors and finishes with
 
CODE START *********************
 movwf BD1IBC, BANKED ; Put right packet size into BD1IBC and BD1OBC
 movwf BD1OBC, BANKED
 movlw 0x88  ; Start with DATA0, toggled after first block of data
 movwf BD1OST, BANKED ;   received. UOWN = 1 as we are always ready to receive data
 movlw 0x48  ; Start with DATA1, toggled before first block of data sent.
 movwf BD1IST, BANKED ; Keep UOWN = 0 so the Ep1Service will notice it, fill up
    ;   the buffer and make UOWN = 1
; *** Lastly set up UEP1 to enable EP1_IN and EP1_OUT
 banksel USB_tmp   ; Reset Bank
 movlw ENDPT_NON_CONTROL ; EP1 set-up; Enable IN/OUT, no control,
     ;    Hshk (toggle) enabled
 movwf UEP1
; Note that EP1_IN(UOWN) is *not* set, but that EP1_OUT(UOWN) is.
CODE STOP ***********************
 
Ep1Service is as follows:
 
CODE START *********************
Ep1Service
 banksel USB_tmp   ; Reset Bank
; Return if USB_USWSTAT != CONFIG_STATE
 movf USB_USWSTAT, W
 xorlw CONFIG_STATE
 btfss STATUS, Z
 return
; Return if device is in SUSPEND mode (i.e. if UCON[1] = 1)
 btfsc UCON, 1
 return
; Now check whether an IN transfer is happening, in which case don't
; muck about with it.
 banksel BD1IST
 btfsc BD1IST, UOWN, BANKED
 bra Ep1Service_01  
; Here if no IN transfer is happening, so we have to assume that
; it is complete and we can get the next packet of data ready.
; This is done by InDataToBuffer
 call InDataToBuffer
; Increment PACKET_IN_NUMBER for next time
 incf PACKET_IN_NUMBER
 banksel BD1IBC
 movlw MAX_PACKET_SIZE
 movwf BD1IBC, BANKED
; Make BD1IST 0x88 or 0xC8 depending on DATA0 or DATA1, respectively.
; Ep1Init made BD1IST = 0x40, so this will toggle it and BD1IST will
; start with 0x88 so EP1_IN(UOWN) will be set.
 movf BD1IST, W, BANKED         
 andlw 0x40
 btg WREG, 0x6
 iorlw 0x88
 movwf BD1IST, BANKED
; EP1_IN should now contain the right data and be primed, ready for the
; PC to collect it.
; Now we have completed our efforts with IN data, so
; let's consider any OUT data
Ep1Service_01
; Now check whether an OUT transfer is happening, in which case
; return and don't muck about with it.
 btfsc BD1OST, UOWN, BANKED
 return  
; Here if no OUT transfer is happening, so we have to assume that
; it is complete and we can clear out the data into local memory,
; This is done by BufferToOutData
 call BufferToOutData
; Increment PACKET_OUT_NUMBER (pointeer to my SRAM area) for next time
 incf PACKET_OUT_NUMBER
; Configure OUT buffer so the host can send
 banksel BD1OBC
 movlw MAX_PACKET_SIZE
 movwf BD1OBC, BANKED
 movlw 0x40
 andwf BD1OST, F, BANKED
 btg BD1OST, 0x6, BANKED
 movlw 0x88
 iorwf BD1OST, F, BANKED
 return
; **** End of routine Ep1Service
 
CODE STOP ***********************
 
I have also implemented a Control transfer that uses a Vendor class.
This moves SET_BLOCK_NUMBER (=0x05, my choice) to USB_dev_req and
nothing else.  That is then picked up by ProcessOutToken which,
if USB_dev_req = SET_BLOCK_NUMBER runs the following code:
 
CODE START *********************
 movlw  NO_REQUEST
 movwf  USB_dev_req  ; Clear device request
 banksel  BD0OAH
 movf  BD0OAH, W, BANKED ; put EP0 OUT
 movwf  FSR0H, ACCESS  ;    buffer
 movf  BD0OAL, W, BANKED ;    pointer
 movwf  FSR0L, ACCESS  ;    into FSR0
; Could we do the next bit with PACKET_STARTS, PACKET_STARTS + 1, PACKET_STARTS + 2 etc?
 lfsr  FSR1, PACKET_STARTS
 movff  POSTINC0, POSTINC1 ; IN packet start, Mid-byte
 movff  POSTINC0, POSTINC1 ; IN packet start, MSByte
 movff  POSTINC0, POSTINC1 ; OUT packet start, Mid-byte
 movff  POSTINC0, POSTINC1 ; OUT packet start, MSByte
; Here I am collecting 4 bytes of data I sent with the Control request that
; will tell me where the IN or OUT data is to go in my external SRAM.
; If we are resetting the address where the data is found, surely we must
; reload the IN buffer as well.  If we don't it'll be sitting there with
; the wrong data pre-loaded.  The scheme for doing this is as follows.
; First I will take back control of the EP1_IN buffer by making
; BD1IST[UOWN] = 0.  Then I will toggle the BD1IST DATA0/1 flag.
; Making BD1IST[UOWN] = 0 could cause trouble if we were actually in the
; middle of an EP1_IN transfer but that shouldn't happen as we can only get
; here from a Control transfer and we can't be doing a Bulk Transfer at the
; same time,
; When Ep1Service sees that BD1IST[UOWN] = 0 it will reload the buffer and
; also toggle DATA0/1.  The toggle would be a bad idea as the last lot of
; data was never sent, so I toggle DATA0/1 here, so the next toggle just
; puts it back where it was.
 banksel  USB_tmp   ; The Bank is reset here
 clrf  PACKET_IN_NUMBER ; Reset PACKET_IN_NUMBER to zero
 clrf  PACKET_OUT_NUMBER ; Reset PACKET_OUT_NUMBER to zero
 banksel  BD0OBC
 bcf  BD1IST, UOWN  ; Grab the EP1_IN endpoint back
 btg  BD1IST, 6  ; Toggle its DATA0/1 value
ProcessOutToken_03 
 banksel  BD0OBC
 movlw  MAX_PACKET_SIZE
 movwf  BD0OBC, BANKED
 movlw  0x88
 movwf  BD0OST, BANKED
 clrf  BD0IBC, BANKED  ; set byte count to 0
 movlw  0xC8
 movwf  BD0IST, BANKED  ; send packet as DATA1, set UOWN bit
 banksel  USB_tmp   ; Reset to Bank 0
 return
; **** End of ProcessOutToken
CODE STOP ***********************
 
After all this I am able to read and write 512kBytes to my SRAM pretty quickly
(it's the PIC to SRAM that is now the bottleneck).
I first have to send my control transfer.  If I make IN_L = OUT_L, and IN_H = OUT_H
I will be writing to, and reading from, the same place in my SRAM, so I can test the
whole thing by making up a file of random data, keeping a note of it, and then sending
to the PIC/SRAM and then reading it back.  If the original and twice-copied
data are the same, I have succeeded.
 
PC "C" CODE START *********************
  tmp1[0] = IN_L; // IN packet start, Mid-byte
  tmp1[1] = IN_H; // IN packet start, MSByte
  tmp1[2] = OUT_L; // OUT packet start, Mid-byte
  tmp1[3] = OUT_H; // OUT packet start, MSByte
  ret = usb_control_msg(dev, 0x40, SET_BLOCK_NUMBER, block, 0, tmp1, 4, 100);
  if (ret < 0) {
    printf("Unable to send vendor request to set block number, ret = %d...\n", ret);
    }
  else
    {
   printf("Vendor request to set block number succeeded\n");
    }
// Set up some random data
  for(j=0; j<sizeof(tmp1); j++) { tmp1[j] = rand() % 256; tmp2[j] = tmp1[j]; }
  for(j=sizeof(tmp1); j<sizeof(tmp2); j++) tmp2[j] = rand() % 256;
  printf("%d %d %d %d %d %d %d\n", tmp2[0], tmp2[1], tmp2[2], tmp2[3], tmp2[4], tmp2[5], tmp2[6]);
  printf("%d %d %d %d %d %d %d\n", \
  tmp2[WRITE_SIZE-7], tmp2[WRITE_SIZE-6], tmp2[WRITE_SIZE-5], tmp2[WRITE_SIZE-4], \
  tmp2[WRITE_SIZE-3], tmp2[WRITE_SIZE-2], tmp2[WRITE_SIZE-1]);
  ret = usb_bulk_write(dev, EP_OUT, tmp2, sizeof(tmp2), 2000);
  if(ret != sizeof(tmp2))
    {
      printf("Error %d: bulk write (OUT) failed\n", ret);
    }
  else
    {
   printf("Bulk write (OUT) succeeded\n");
    }
   tmp1[0] = IN_L; // IN packet start, Mid-byte
   tmp1[1] = IN_H; // IN packet start, MSByte
   tmp1[2] = OUT_L; // OUT packet start, Mid-byte
   tmp1[3] = OUT_H; // OUT packet start, MSByte
// Resend the start address.  This has the side effect of causing the
// IN buffer in the PIC to reload.
  ret = usb_control_msg(dev, 0x40, SET_BLOCK_NUMBER, block, 0, tmp1, 4, 100);
  if (ret < 0) {
    printf("Unable to send vendor request to set block number, ret = %d...\n", ret);
    }
  else
    {
   printf("Vendor request to set block number succeeded\n");
    }
// The next bit is just setting tmp1 to values that I do *not* want to see
// again, as usb_bulk_read should overwrite them
 tmp1[0] = 19; tmp1[1] = 19; tmp1[2] = 19; tmp1[3] = 19; tmp1[4]=19;
  ret = usb_bulk_read(dev, EP_IN, tmp1, sizeof(tmp1), 2000);
  if(ret != sizeof(tmp1))
    {
      printf("Error %d: bulk read (IN) failed\n", ret);
    }
  else
    {
   printf("Bulk read (IN) succeeded\n");
    }
  printf("%d %d %d %d %d %d %d\n", tmp1[0], tmp1[1], tmp1[2], tmp1[3], tmp1[4], tmp1[5], tmp1[6]);
  printf("%d %d %d %d %d %d %d\n", \
  tmp2[READ_SIZE-7], tmp2[READ_SIZE-6], tmp2[READ_SIZE-5], tmp2[READ_SIZE-4], \
  tmp2[READ_SIZE-3], tmp2[READ_SIZE-2], tmp2[READ_SIZE-1]);
  usb_release_interface(dev, 0);
  usb_close(dev);
PC "C" CODE STOP *********************
#10
newfound
Super Member
  • Total Posts : 1827
  • Reward points : 0
  • Joined: 2003/11/07 12:35:49
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/07 23:11:20 (permalink)
0
Congratulations Martin! Now that you have a working base you can tidy things up and build on them.

Remember if you are posting code use the code tags so it far more readable. Great that you want to pay it forward.

Thanks for the feedback and for considering the advice given and working hard to get where you wanted. Well done.
#11
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 07:22:08 (permalink)
0
To anyone reading this, here is an update.  It's just a chat about what I got up to - I hope it's useful to someone.
 
What I had above seemed to work well, but when I pushed it a bit further there were problems.
 
Writing data to the PIC (OUT transfer) was easy enough, but reading data was tricky.
 
I have a 512kByte SRAM attached to my PIC and I want to read its contents up to the PC.  As I am using full-speed USB the maximum packet size is 64 bytes, so a lot of packets are required.  Initially I did the job using control transfers, but that was S-L-O-W, so I started using bulk transfers.
 
I decided to work in 4kByte blocks, because there is a sort-of USB limit/breakpoint on 4kByte, so I had 128, 4kByte, blocks in my SRAM memory. My aim was to send a control message identifying a block, and then to use usb_bulk_read to read the 4096-byte block.  The PC splits this 4096-byte block into a (fast) series of 64, 64-byte packets (4096/64 = 64).
 
Now a USB bulk read "transaction" comprises a setup packet, which is an IN token, followed by data, followed by an ACK.  Once *all* of that is done the TRNIF flag is raised (and an interrupt generated if interrupts are being used, which I am not).
 
When the usb_read_bulk command starts off, the PIC gets a Setup packet which is an IN token, and the PIC reacts by sending the data.  If that worked the PIC sends an ACK, and then the TRNIF flag is raised.
 
Note that there was no time during this sequence for me to get my data ready in the EP1_IN buffer.  Therefore it *must* be put in there *before* the usb_bulk_read command is sent.
 
I therefore have to arrange that the control message I sent to get the block number right, also fills the EP1_IN buffer.  This may mean that it overwrites something that was already there, but it's easy enough to do.
 
Now, when do I want the next chunk of 64-bytes to be put into the EP1_IN buffer?  That should be done after the end of the transaction, i.e. when the TRNIF flag is set.  I therefore include a section in the routine that deals with the TRNIF flag, that refills the buffer.  It should only do this when it gets a TRNIF that comes from EP1_IN, of course.
 
Every time a bulk transaction is dealt with the DATA0/1 flag is toggled, and a similar flag is also toggled at the PC end.  So long as one packet is read from the PIC for every one packet received by the PC, and they both toggle their flags, the PIC and PC flags should be equal.  Otherwise there *should* be an error condition.
 
The DATA0/1 toggle sequence starts at 0 when the USB is reset (enumerated), so when we initialise things we should make DATA0/1 = 0 on the PIC.
 
Once the first block number has been received, and we are about to read the first data since enumeration, we have to load the EP1_IN buffer. What do we do with DATA0/1?  Leave it alone.  It will have been set to zero when the USB enumerated.
The first 64-byte packet is read.  When that is complete the TRNIF flag will be set.  When we are dealing with that we should load the EP1_IN buffer *and* toggle DATA0/1.  We then return control of the USB buffer to the SIE (make UOWN = 1) and wait.  The PC will then read the next 64-byte packet.
 
This sequence continues until, in my case, the PC has read 64, 64-byte, packets. Then the usb_bulk_read has read all it is going to read (for now) and stops.  At the end of the last packet the TRNIF flag was set, as usual, we loaded the EP1_IN buffer with the next 64-byte packet of data, and DATA0/1 was toggled.
 
I now use my control message to send a different block number to the PIC.  When this message arrives the PIC should use the code that deals with the OUT token to also load the buffer with the correct data, i.e. the first 64-bytes of the block I have now requested.  This will replace the 64-bytes loaded into EP1_IN when the last transaction ended.
 
What do I do with DATA0/1?  Nothing.  It is already OK, because it was toggled at the end of the last transaction.  All I have to do is replace the data in the EP1_IN buffer with the data from the block I am now wanting, the DATA0/1 value will be fine.  I decided to seize control of the USB buffers back from the SIE at this stage by making UOWN = 0.  This stops the PC trying to read data I am busy changing.  Remember to give control back to the SIE when the buffer is correctly filled.
 
So, to recap, when I receive a new block number I have to read data from my SRAM and put it into EP1_IN, but at that time I do not toggle the DATA0/1 flag.
 
I must *also* read data from my SRAM and put it into EP1_IN when TRNIF is set, at the end of an EP1_IN transaction.  At this point I must also toggle the DATA0/1 flag.
 
All this is not helped by the observed fact that the usb_bulk_read in the libusb-win32 libraries does not seem to notice if DATA0/1 is incorrect.  If DATA0/1 is incorrect the PIC returns an ACK, but that's all.  The use_bulk_read routine picks up 64-bytes of rubbish from somewhere, and returns it to the PC with a (correct) return value of 64, the number of bytes read.  The PC stores this rubbish as though it was good data, toggles its DATA0/1 flag, and asks for the next 64 bytes.  The PIC now sees the correct DATA0/1 value and responds with the good data it had ready last time, but didn't send as the DAT0/0 value was wrong.  The DATA0/1 values on the PIC and PC are now in synch and all is well.  *However* I now have 64 bytes of rubbish in the data held on my PC.
 
I don't know how to persuade usb_bulk_read to tell me that the DATA0/1 value was wrong so I can do something about it.  It may be that on the  Linux version of libusb this all works fine, but I don't know.
 
So, the code I gave above *nearly* works, but it gets out of synch sometimes.  I have made the following big changes:
 
1/ I have put my EndPoint initialisation code (EpInit) back into the
   StandardRequests routine under the point where we have
       movlw CONFIG_STATE  ; CONFIG_STATE = 3
       movwf USB_USWSTAT
2/ In EpInit I make DATA0/1 = 0
3/ In ProcessOutToken, if USB_dev_req = SET_BLOCK_NUMBER, which is the
   USB_dev_req value I send when I want to enter a new block number, I
   reload the EP1_IN buffer.
4/ In ServiceUsb, where it deals with the TRNIF flag, and if the
   last token was an IN token, ProcessInToken is called.  In there,
   and only if I am dealing with EP1, I load the EP1_IN buffer from
   SRAM and toggle DATA0/1
5/ My Ep1Service routine now deals with write (OUT) operations only,
   and I may move its contents back into one of the ProcessToken routines
 
I hope this makes some kind of sense to readers because it has taken me some time to sort it out in my head.  I'm now off to see if I can apply some of the lessons learned to isochronous transfers.  I do wish there were some notes about them available somewhere!

Martin
 
PS: If any expert finds errors in this please note them down.  I won't feel bad about it and will learn  I'm just showing off my scars here, hoping that I can help other folk avoid getting the same ones!
#12
xiaofan
Super Member
  • Total Posts : 6247
  • Reward points : 0
  • Joined: 2005/04/14 07:05:25
  • Location: Singapore
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 08:04:42 (permalink)
0
ORIGINAL: ee_martin
I hope this makes some kind of sense to readers because it has taken me some time to sort it out in my head.  I'm now off to see if I can apply some of the lessons learned to isochronous transfers.  I do wish there were some notes about them available somewhere!


For isochronous transfer, there will be no data toggle problem as it is no use anyway in isochronous transfer. If you want to make a generic isochronous transfer device, be prepared to face significant difficulties in the host software front. The APIs for libusb-win32 isochronous transfer is very poor.

There are firmware examples from Microchip now for isochronous transfer so you are lucky now. But they are for audio device. Please refer to our struggle back in 2007.
http://forum.microchip.com/tm.aspx?m=270049

Please also refer to the links in the USB links thread.
http://www.microchip.com/forums/tm.aspx?m=123533

  USB_Links and libusb
#13
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 08:27:07 (permalink)
0
Xiaofan,
 
I realise I won't have the DATA0/1 problem in isochronous transfer, but everything else *is* a
problem!  I did get something going, but it was very poor and I've a long way to go.
 
What I am missing is the "big picture" stuff for isochronous.  There seem to be three main routines:
usb_isochronous_setup_async, usb_submit_async and usb_reap_async.
The first seems to set up the contents of the structure that is its second argument.
The second seems to set up this particular transfer and
the last seems to be the one that collects the data,
but I'm not sure even of that.
 
And where is the data going, and why are the buffers so large?  There is talk of circular buffers
that you have to keep emptying.  Does the isochronous transfer take place *automatically*
every frame (i.e. every ms).
 
I feel I would have a much better chance with isochronous if I could see the picture at this
level.  I can usually sort out the detail out I have the big picture.
 
Martin
 
 
#14
stefanopod
Super Member
  • Total Posts : 1285
  • Reward points : 0
  • Joined: 2007/06/25 02:33:59
  • Location: Bologna,Italy
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 09:37:20 (permalink)
0
An addition to xiaofan's post:
even adapting microchip examples for audio to generic isochronous, I couldn't avoid a consistent rate of dropped packets.
Bulk transfer is much safer, from this point of view.


provando e riprovando
#15
xiaofan
Super Member
  • Total Posts : 6247
  • Reward points : 0
  • Joined: 2005/04/14 07:05:25
  • Location: Singapore
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 17:03:37 (permalink)
0
ORIGINAL: ee_martin
Does the isochronous transfer take place *automatically*
every frame (i.e. every ms).


Yes. As for the libusb-win32 API for isochronouns transfer (asynchronous I/O), it is really not well documented. Basically you are on your own. Or you can try to read the source codes.
http://libusb-win32.svn.sourceforge.net/viewvc/libusb-win32/trunk/libusb/src/

If you are using Linux and libusb 1.0, probably you will have better chance. The APIs are better documented.
http://libusb.sourceforge.net/api-1.0/group__asyncio.html

There is an efforts to have libusb 1.0 working under Windows. But the first backend is for WinUSB. The second backend is for HID. Both do not support isochronous transfer. So there will be no isochronous transfer support for quite some time even if libusb 1.0 is ported to Windows.

  USB_Links and libusb
#16
xiaofan
Super Member
  • Total Posts : 6247
  • Reward points : 0
  • Joined: 2005/04/14 07:05:25
  • Location: Singapore
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/01/27 17:07:53 (permalink)
0
I once asked this question but got no answer. It is an example I wrote based on "Copy and Paste" but I do
not understand it well.

http://article.gmane.org/gmane.comp.lib.libusb.devel.windows/1439

The example we did ( http://forum.microchip.com/tm.aspx?m=270049 ) is not a proper device actually. The real one will have one default interface which does not require bandwidth.
http://old.nabble.com/An-urgent-query-regarding-LIBUSB-td17080907.html
post edited by xiaofan - 2010/01/27 17:13:52

  USB_Links and libusb
#17
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/02/09 09:56:02 (permalink)
0
Xiaofan,
 
I have set about the isochronous stuff and am making a *little* bit of progress.  To my surprise I find that reading from the PIC (so an IN transfer) is OK, but I can't write to it.  In the past I've always found writing easier.  The code you have kindly made available has the "write" section commented-out.  Did you get it to work?
 
My "write" code is as follows:
 

#define EPOUT 0x02
#define ISO_OUT_PACKET_SIZE 256

 void test_write_isochronous_async(usb_dev_handle *dev)
 {
 unsigned char buf0[ISO_OUT_PACKET_SIZE];
 int data_cnt, ret, i;
 void *context0 = NULL;
 
ret = usb_isochronous_setup_async(dev, &context0, EPOUT);
 printf("ret from usb_isochronous_setup_async in write routine is %d\n\n", ret);
 printf("START-ISO-WRITE-TEST\n\n");
 
// Now make up some recognisable data to write:
for(i = 0; i < ISO_OUT_PACKET_SIZE; i++) {
  buf0[i] = (i * 3) % 256;
 }
 
 ret = usb_submit_async(context0, (char*)buf0, ISO_OUT_PACKET_SIZE);
 if(ret < 0) printf("usb_submit_async in write routine failed with error %d\n", ret);
 else printf("usb_submit_async in write routine succeeded with return value %d\n\n", ret);
 
 data_cnt = usb_reap_async(context0, 500);

 if(data_cnt < 0) printf("ISO-WRITE-TEST-OUT failed with error %d\n\n", data_cnt);
 else printf("ISO-WRITE-TEST-OUT succeeded with return value %d\n\n", data_cnt);
 
data_cnt = usb_reap_async(context0, 500);
 
if(data_cnt < 0) printf("ISO-WRITE-TEST-OUT failed with error %d\n\n", data_cnt);
 else printf("ISO-WRITE-TEST-OUT succeeded with return value %d\n\n", data_cnt);
}

 
The read stuff works, and the output from the write section is:
 

ret from usb_isochronous_setup_async in write routine is 0
START-ISO-WRITE-TEST
usb_submit_async in write routine succeeded with return value 0
ISO-WRITE-TEST-OUT succeeded with return value 256
ISO-WRITE-TEST-OUT succeeded with return value 256
ret from usb_isochronous_setup_async in read routine is 0

which all looks OK.  However, the data I tried to write is not in the PIC memory anywhere.  It's hard for me to get that wrong on the PIC side, since I do not try to do anything with the data I wrote (so I can't mess up the data while transferring it from the buffer to somewhere else).  I have another system that uses control transfers to simply read out all the data between 0x480 and 0x7FF in the PIC memory, and I can't see what I tried to write anywhere.  What I do see is the data I wrote in that memory using instructions on the PIC that are run after a reset, so the data in 0x480 - 0x7FF is completely undisturbed.
 
Am I using the correct sequence, i.e.
 

usb_isochronous_setup_async(dev, &context0, EPOUT);
usb_submit_async(context0, (char*)buf0, ISO_OUT_PACKET_SIZE);
usb_reap_async(context0, 500);

This is the same sequence as I use to read from the PIC, except that I use EPOUT in the setup routine instead of EPIN.  Is that enough so that subsequent commands become write operations instead of read operations?  The "reap" command sounds more like a read than a write.
 
Any help would be much appreciated.
 
Martin
 
#18
xiaofan
Super Member
  • Total Posts : 6247
  • Reward points : 0
  • Joined: 2005/04/14 07:05:25
  • Location: Singapore
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/02/09 16:49:55 (permalink)
0
I did not test the write function last time. But the sequence is correct (setup, submit, reap, free).

Another example for you (from vloki, both read and write using libusb-win32).
http://www.microchip.com/forums/tm.aspx?m=372725

 from vloki, bare minimum SPP isochronous transfer example
// TEST_ISO.cpp : main project file.

#include "stdafx.h"

//using namespace System;
/*
int main(array<System::String ^> ^args)
{
   Console::WriteLine(L"Hello World");
   return 0;
}
*/
////////////////////////////////////////////////////////////////////////////////

#include "usb.h"


#define VERSION "0.1.0"
#define VENDOR_ID 0x04D8
#define PRODUCT_ID 0x0080
#define INTRFACE 0

usb_dev_handle *find_picdem_isoc();
usb_dev_handle* setup_libusb_access();
void test_read_isochronous_async(usb_dev_handle *dev);
void test_write_isochronous_async(usb_dev_handle *dev);


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char **argv)
{
   usb_dev_handle *picdem_isoc;
   if ((picdem_isoc = setup_libusb_access()) == NULL) {
       exit(-1);
   }

   test_read_isochronous_async(picdem_isoc);
   test_write_isochronous_async(picdem_isoc);


// ### release interface
   usb_set_altinterface(picdem_isoc, 0);
   usb_release_interface(picdem_isoc, 0);
   usb_close(picdem_isoc);
  
   while(1);
   return 0;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void test_read_isochronous_async(usb_dev_handle *dev)
{
   unsigned char buf0[4096*8-128];//128];//
   unsigned char buf1[4096];
   unsigned char buf2[4096];

   int data_cnt;

// ### use three contexts for this example (more can be used)
   void *context0 = NULL;
   void *context1 = NULL;
   void *context2 = NULL;

 
// ### read transfer (stream)
   usb_isochronous_setup_async(dev, &context0, 0x81,128);   
   usb_isochronous_setup_async(dev, &context1, 0x81,128);
   usb_isochronous_setup_async(dev, &context2, 0x81,128);

   printf("\n\r\n\rSTART-ISO-READ-TEST");

   for(int i = 0; i < 5; i++)
   {
       printf("\n\r");
       usb_submit_async(context0, (char*)buf0, sizeof(buf0));
       data_cnt = usb_reap_async(context0, 5000);
       printf("ISO-READ-TEST-OUT"" %02d, 0, %02d, \n\r", i, data_cnt);
       for(int j = 0; j < 128; j++)
           printf(" %02x ", buf0[j]);
   Sleep(300);
/*
       printf("\n\r");
       usb_submit_async(context1, (char*)buf1, sizeof(buf1));
       data_cnt = usb_reap_async(context1, 5000);
       printf("ISO-READ-TEST"" %02d, %02d, \n\r", i, data_cnt);
       for(int j = 0; j < data_cnt; j++)
           printf(" %02x ", buf1[j]);

       printf("\n\r");
       usb_submit_async(context2, (char*)buf2, sizeof(buf2));
       data_cnt = usb_reap_async(context2, 5000);
       printf(" %02d, \n\r ", data_cnt);
       for(int j = 0; j < data_cnt; j++)
           printf(" %02x ",buf2[j]);
*/
   }
// end read
   usb_free_async(&context0);
   usb_free_async(&context1);
   usb_free_async(&context2);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void test_write_isochronous_async(usb_dev_handle *dev)
{
  unsigned char buf0[4096];
  unsigned char buf1[4096];
  unsigned char buf2[4096];

  int data_cnt;

// ### use three contexts for this example (more can be used)
  void *context0 = NULL;
  void *context1 = NULL;
  void *context2 = NULL;

// ### write transfer (stream) #################################################

  usb_isochronous_setup_async(dev, &context0, 0x01,128);
  usb_isochronous_setup_async(dev, &context1, 0x01,128);
  usb_isochronous_setup_async(dev, &context2, 0x01,128);

   printf("\n\r\n\rSTART-ISO-WRITE-TEST \n\r");

   for(int i = 0; i < 3; i++)
   {
       usb_submit_async(context0, (char*)buf0, sizeof(buf0));
       data_cnt = usb_reap_async(context0, 5000);
       printf("ISO-WRITE-TEST-OUT"" %02d, %02d, \n\r", i, data_cnt);
   Sleep(300);
/*
       usb_submit_async(context1, (char*)buf1, sizeof(buf1));
       data_cnt = usb_reap_async(context1, 5000);
       printf("ISO-WRITE-TEST-OUT"" %02d, %02d, \n\r", i, data_cnt);

       usb_submit_async(context2, (char*)buf2, sizeof(buf2));
       data_cnt = usb_reap_async(context2, 5000);
       printf("ISO-WRITE-TEST-OUT"" %02d, %02d, \n\r", i, data_cnt);
*/     }
// end write

   usb_free_async(&context0);
   usb_free_async(&context1);
   usb_free_async(&context2);
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
usb_dev_handle* setup_libusb_access() {
   usb_dev_handle *picdem_isoc;

   usb_set_debug(255);

   usb_init();
/* Just like the name implies, usb_init sets up some internal structures.
  usb_init must be called before any other libusb functions.*/

   usb_find_busses();
/* usb_find_busses will find all of the busses on the system.
  Returns the number of changes since previous call to this function
  (total of new busses and busses removed).*/
  
   usb_find_devices();
/* usb_find_devices will find all of the devices on each bus.
  This should be called after usb_find_busses.
  Returns the number of changes since the previous call to this function
  (total of new device and devices removed).*/


   if(!(picdem_isoc = find_picdem_isoc())) {
       printf("Couldn't find the mouse, Exiting\n");
       return NULL;
   }

   if (usb_set_configuration(picdem_isoc, 1) < 0) {
       printf("Could not set configuration 1 : %s\n");
       return NULL;
   }
  
   if (usb_claim_interface(picdem_isoc, INTRFACE) < 0) {
       printf("Could not claim interface: %s\n");
       return NULL;
   }

   return picdem_isoc;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
usb_dev_handle *find_picdem_isoc()
{
   struct usb_bus *bus;
   struct usb_device *dev;

   for (bus = usb_busses; bus; bus = bus->next) {    //usb.h #define usb_busses usb_get_busses()

       for (dev = bus->devices; dev; dev = dev->next) {
              if (dev->descriptor.idVendor == VENDOR_ID &&
                 dev->descriptor.idProduct == PRODUCT_ID ) {
               usb_dev_handle *handle;
                 printf("picdem_isoc with Vendor Id: %x and Product Id: %x found.\n", VENDOR_ID, PRODUCT_ID);
               if (!(handle = usb_open(dev))) {
                   printf("Could not open USB device\n");
                   return NULL;
               }
               return handle;
           }
       }
   }
   return NULL;
}

#19
ee_martin
Starting Member
  • Total Posts : 56
  • Reward points : 0
  • Joined: 2007/10/18 13:20:44
  • Location: 0
  • Status: offline
RE: USB Bulk transfer on PIC18F4550 2010/02/10 01:14:38 (permalink)
0
Xiaofan,
 
Thanks for that information - I'll keep at it.
 
Last night I got to the stage where exactly the first four bytes of the 256 I sent to the PIC arrived in the right place.  The other 252 bytes disappeared!  This was after some fiddling with the PIC code, and I have no idea how it happened.  I don't know how I could do it if I *wanted* to do it!
 
I'll try again tonight when I'm not so tired.
 
All the best,
 
Martin
 
#20
Page: 12 > Showing page 1 of 2
Jump to:
© 2019 APG vNext Commercial Version 4.5