PDA

View Full Version : Problem with DT_HID (maybe!)



awmt102
- 27th July 2011, 15:10
Hi all,

Im having a little trouble using Darrel Taylors USB DT_HID "framework".

Apologies for the long winded post, but I want to give as much information as possible as opposed to the classic "My code doesn't work, HELP!" ones you often see.

Everything enumerates OK and my device transmits data correctly but receiving data is a different story. I have defned a set of 'messages' to allow simple communication between host and device. The messages take the form of two bytes - ID and data. Depending on what ID is received the approriate action is taken, e.g. Device receives Message ID "F", which indicates it should repsond by sending its firmware version string. In order to make sure errors are handled and reported if the device does not recognise the command it receives it responds by sending Message ID "u" and data = the message ID it does not recognise. The host software then alerts the user that an unknown command was sent to the devce.

My problem is that any command I send results in the device responding with a "u" and what initially seemed to be a nonsense byte. It also seemed that this byte remained constant no matter what unrecognised command I sent e.g. I send 0x02 0x00 and I get the response 0x75 0x43 (equivalent to "u" "+"), then I send 0x03 0x00 and I still get 0x75 0x43 returned.

However I later realised that if I reset my device the next time it responds the unknown command is set to whatever the last unknown command I sent was. This may sound confusing so I will illustrate:




Device reprogrammed here...

Host sends: Device Responds:
============================
0x02 0x00 0x75 0x43
0x02 0x00 0x75 0x43
0x03 0x00 0x75 0x43
0x03 0x00 0x75 0x43

Device reset here.....

0x02 0x00 0x75 0x03
0x02 0x00 0x75 0x03
0x03 0x00 0x75 0x03
0x03 0x00 0x75 0x03
0x04 0x00 0x75 0x03

Device reset again...

0x03 0x00 0x75 0x04
0x70 0x00 0x75 0x04 <-- 0x70 is a valid command but is not being recognised
etc.


So as far as I can reason there are two possible problems - the Firmware on the device is somehow storing the unknown command but only recalling it when the device is reset and then freezing the buffer again until another reset - seems very unlikely since I do not believe the USB module uses non-volatile memory, unless toggling !MCLR is not actually resetting the USB memory?

Or the device is working fine but the host software is failing to update its OUT buffer when I wish it to and is instead repeating the previous send until the device is reset whereupon it finally updates the buffer with the most recent requested bytes. This seems like the most intuitive simply because I would expect resetting the device would destroy any past knowledge of bytes I have sent. However I have used a USB analyser program and it suggests I am sending what I am trying to send and receiving what I think I am receiving. Assuming this is monitoring at the lowest level then it must be the device that is incorrect.

Basically I am at hair pulling frustration levels! If anyone knows exactly whats wrong then great! If not then any suggestions on what I could try would be welcome.

Heres a description of my setup:

PBP 2.60 developing in MPLAB. PIC18F2550, 4MHz Clock.
DT_HID260.pbp - modified slightly for my needs - changed the Usage Page information so that my software can pick up Windows Messages from it more easily
VB6.0 host software.

Attached is my firmware code. If you need me to I can attach some of the vb 6 code but its an activex which makes it a little complicated - let me know if it would be helpful.

Thanks in advance!

awmt102
- 8th August 2011, 13:36
OK, so obviously nobody has any thoughts so far but I have done a little more investigation and found out the probem lies with me using an interrupt to perform the reads...

I started from scratch using Darrells BasicUSB.pbp test program adapted for pic18f2550 4MHz. I wrote a very simple loop that checks for Rx data then sends out the RX buffer straight back to the PC. It sits there quite happily spewing out whatever the last RX data was, everytime I send a new set of data it updates correctly and everything works as expected.

I started adapting this simple routine to become more like my original (faulty) code. The first step was to take the DoUSBIn out of the main loop and into an interrupt driven by TMR2. So now the program runs the main loop where it echoes whatever is in the RX buffer, pauses for 100ms and repeats, whilst TMR2 interrupts when it overflows (no preload, it just keeps running) and checks for received data using the @ ON_USBRX_GOSUB macro. This is where it seems to fail. Basically the data I send the device does not get copied over into the Tx buffer. I also get the same effect as before where after a reset the TX buffer contains whatever the previous RX data was.

I have confirmed that the PC end is working as expected by using a variety of different methods of communicating the device. It is definately the PIC that is not operating as expected.

So I think there is something funny going on with the interrupts, either I am missing some statements somewhere or DT_HID is doing something funny.

Does anybody have any thoughts?

Thanks

Andy

Darrel Taylor
- 9th August 2011, 00:38
Andy,

When you GOSUB from a high priority interrupt handler by using @ ON_USBRX_GOSUB ... all interrupts stop.
USB is no longer being serviced. Nothing can be transmitted or received.

When you hang around blinking LED's and PAUSING for several seconds, the USB connection will be dropped. Actually it will be dropped much sooner than that.

You might get away with it if tick_timer: was a low priority interrupt, but certainly not the way you have it.

awmt102
- 10th August 2011, 14:28
Hi Darrel

Thanks for the reply, that certainly makes some sense. However I have replaced my tick timer routine with this one:


tick_timer:
USBSERVICE
USBIn 1, USBRXBuffer, USBRXBufferCount, NoData ' read data, if available
USBSERVICE
goto DataOK
NoData:
' zero out the USBRXBuffer[0] byte
USBRXBuffer[0] = 0
DataOK:
@ INT_RETURN

And in the main loop I can check USBRXBuffer[0] for a non zero value which indicates a valid command has been received and I can act on it. Given that the only thing not USB related is setting that byte to 0 I would not have thought that the device would drop off the bus, especially given that this is the 'traditional' way of handling USBSERVICE routines. I have also set the tick_timer interrupt to be Low Priority.

However this suffers from the same problem as before. Any thoughts?

Thanks

Andy

Darrel Taylor
- 11th August 2011, 05:47
If you've set that to low priority, then the high priority interrupts will interrupt that routine and service the USB in the middle of a USBSERVICE statement.
That will knock out the USB in short order.

When you are servicing USB via interrupts, you cannot have a USBSERVICE statement anywhere else in your program.
Only in the USB_INT or other high priority handlers that can't be interrupted.

Darrel Taylor
- 11th August 2011, 06:16
Why do you need to check for incomming USB reports every ~340uS ?

awmt102
- 16th August 2011, 10:29
There is no specific timing requirement, the period is just the maximum Timer2 would allow.

I realise that I can of course just resort to checking for incoming traffic in the main loop and in this instance that works OK. However there is obviously a restriction here that it would nice to be able to solve, i.e. how do you perform a USB read triggered by an interrupt?

I suppose that in the ideal case the receipt of USB data would trigger an interrupt of its own where the data can be processed, therefore negating the need for polling (that I do not consider to be very elegant!).

Do you think this is something worth exploring? Or is it a bit of a lost cause?

Darrel Taylor
- 19th August 2011, 03:15
PIC's don't generate interrupts on received USB reports.
The only indication a report was received is the buffer ownership bits which are controlled by the SIE.
So at some level, those bits must be polled.

They can be polled in the ISR, and in fact they are already being polled by DT_HID. That's how it maintains the RX_READY and TX_READY flags.
But there isn't enough time there to actually handle incomming data during the ISR because it stops USB from being serviced.
Even if you set a flag and handle it in the main loop, then that flag must be polled instead and nothing is gained except for the release of the ISR.

So the only possibility for immediate response to incoming USB data is to use a Low Priority interrupt for received reports. The USB can continue being serviced by the High Priority interrupts and receive the next report while the last data is parsed and acted upon.
But again, there are no interrupts generated for received reports. So, how to start a Low Priority Interrupt when a report arrives is the real question.

Many of the Interrupt Flags are writable (some are not). You can write a 1 to the xxxIF bit and trigger an interrupt, even though the hardware associated with the flag is disabled.

If you chose a peripheral that is not being used and will not change the flag, you can borrow its flag for your own use. (INTx, IOC, A/D etc. are not good candidates)

On the 18F4550, I would guess the HLVD interrupt will probably not be used by most programs. So in this program, I've used it to generate a Low Priority interrupt when the buffer owner bits have been polled in the High Priority ISR. Then as soon as the H.P. Int finishes the L.P. Int starts. And the L.P. can be interrupted again by the H.P.

I've aliased the HLVD_INT to USBRX_INT for readability by using the DEFINE just before the INT_LISTs. USBRX_INT is then used for the Low Priority interrupt handler.

Another Handler for USB_INT was added to the HighPriority INT_LIST.
That runs the USBRX_CHECK routine at the end of the source code.
The Reset Flag option is set to NO, so the second USB_INT will still execute too.

The end goal is to call the HandleRX subroutine immediately upon arrival of a USB report from the PC.
That, it does.

I used a LAB-XUSB developement board with an 18F4550 for testing.


'************************************************* **************************
'* Name : USBRX_INT.PBP *
'* Author : Darrel Taylor *
'* Date : 8/17/2011 *
'* Version : 1.0 *
'* Target : 18F4550 *
'* Compiler : PBP3 *
'************************************************* **************************
CLEAR
DEFINE OSC 48 ; Internal processor frequency
#CONFIG
CONFIG FOSC = HSPLL_HS
CONFIG PLLDIV = 5
CONFIG USBDIV = 2
CONFIG CPUDIV = OSC1_PLL2
CONFIG FCMEN = OFF, IESO = OFF, PWRT = ON, BOR = OFF
CONFIG VREGEN = ON, WDT = OFF, CCP2MX = OFF, PBADEN = OFF
CONFIG MCLRE = ON, STVREN = ON, LVP = OFF, XINST = OFF
#ENDCONFIG
DEFINE ADC_BITS 10 'Number of bits in ADCIN result
;---[Setup Interrupts]------------------------------------------------------
DEFINE USE_LOWPRIORITY 1 ; Use Low Priority Interrupts
INCLUDE "DT_INTS-18.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ; Allows re-entry to PBP from ASM interrupts
INCLUDE "ReEnterPBP-18LP.bas" ; Include if using Low Pr. PBP INTS
ASM
#define USBRX_INT HLVD_INT ; use HLVD as false Low Priority INT

;----[High Priority Interrupts]---------------------------------------------
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler USB_INT, USBRX_CHECK, ASM, no
INT_Handler USB_Handler
INT_Handler TMR1_INT, _TMR1_ISR, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
INT_ENABLE TMR1_INT ; Enable Timer 1 Interrupts
;----[Low Priority Interrupts]----------------------------------------------
INT_LIST_L macro ; IntSource, Label, Type, ResetFlag?
INT_Handler USBRX_INT, USB_Receive, PBP, yes
endm
INT_CREATE_L ; Creates the Low Priority interrupt processor
INT_ENABLE USBRX_INT
ENDASM
;---[Setup USB]-------------------------------------------------------------
INCLUDE "DT_HID260.pbp"
DEFINE USB_VENDORID 6017
DEFINE USB_PRODUCTID 2000
DEFINE USB_VERSION 2
DEFINE USB_VENDORNAME "Darrel Taylor"
DEFINE USB_PRODUCTNAME "USBRX_INT"
DEFINE USB_SERIAL "001"
DEFINE USB_INSIZE 64 ; IN report is PIC to PC (8,16,32,64)
DEFINE USB_OUTSIZE 16 ; OUT report is PC to PIC
DEFINE USB_POLLIN 10 ; Polling times in mS, MIN=1 MAX=10
DEFINE USB_POLLOUT 10
; --- Each USB LED is optional, comment them if not used ----
DEFINE USB_LEDPOLARITY 1 ; LED ON State [0 or 1] (default = 1)
DEFINE USB_PLUGGEDLED PORTD,0 ; LED indicates if USB is connected
DEFINE USB_TXLED PORTD,1 ; " " data being sent to PC
DEFINE USB_RXLED PORTD,2 ; " " data being received from PC
ADval VAR WORD
;----[Initialize]-----------------------------------------------------------
ADCON1 = %1101 ; AN0 and AN1 Analog
ADCON2.7 = 1 ; Right justify A/D results
T1CON = $31 ; Prescaler = 8, TMR1ON
;----[Main Program Loop]----------------------------------------------------
Main:
PAUSE 200
;--Read AN0--
ADCIN 0, ADval
ARRAYWRITE USBTXBuffer,[6,0,"0 ",DEC ADval,0]
GOSUB DoUSBOut
;--Read AN1--
ADCIN 1, ADval
ARRAYWRITE USBTXBuffer,[6,0,"1 ",DEC ADval,0]
GOSUB DoUSBOut
GOTO Main
;----[USB - Handle received reports]----------------------------------------
; This is called from a low priority interrupt whenever a report is rcvd
HandleRX:
SELECT CASE USBRXBuffer(0)
CASE "1" : SOUND PORTC.2, [100, 50]
CASE "2" : SOUND PORTC.2, [120, 50]
CASE "3" : TOGGLE PORTD.3
CASE "4" : ; send string back to PC
ARRAYWRITE USBTXBuffer,[5," ",STR USBRXBuffer(1)\USBBufferSizeRX-1]
GOSUB DoUSBOut
END SELECT
PAUSE 100
RETURN
;----[Timer1 interrupt blinks an LED]--------------------------------------
TMR_DIV VAR BYTE
TMR1_ISR:
TMR_DIV = TMR_DIV + 1
if TMR_DIV = 12 THEN
TOGGLE PORTD.3
TMR_DIV = 0
ENDIF
@ INT_RETURN
;===[Generates false L.P. interrupt on USB received reports]=*===*===*===*=
ASM
USBRX_CHECK
btfss INTCON, GIEL ; if low priority ints are available
goto Check_Done
btfss _Plugged ; and USB is connected
goto Check_Done
btfss _RX_READY ; and report has been received
goto Check_Done
btfsc _RX_Rcvd ; and the RX buffer is released
goto Check_Done
bsf USBRX_INT ; set the false interrupt flag
Check_Done
INT_RETURN
;
USB_Receive
ON_USBRX_GOSUB _HandleRX
INT_RETURN
ENDASM

awmt102
- 22nd August 2011, 15:18
Hi Darrel,

I realised that there was no dedicated USB RX interupt, hence my problem, but that is a very nice little workaround.

It works perfectly in my current setup and will no doubt be useful in the future. Thanks for all the help!

Cheers

Andy