We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
Hi,
I'm using uVision 4 with the MCB2300 development board. I'm developing a software that periodically transmits data (status information) from the device to the host (as first step, the data are sent without being explicitly requested by the host as soon as a connection has been established). Thereto I've modified the USBCDC example included with uVision. The software transmits 20 bytes periodically, using USB_WriteEP (in usbhw.c, which uses the register interface). This works fine so far. Now I would like to use the DMA engine to avoid copying the bytes word by word into the register. I've enabled DMA in the USB configuration for logical IN endpoint 2 (physical endpoint 5 which is called CDC_DEP_IN in the example), and I have managed to set up DMA descriptors. But how do I trigger a DMA transfer? I've tried several combinations of USB_DMA_Setup and USB_DMA_Enable. Manually setting the appropriate bit in DMA_REQ_SET to trigger the interrupt results in an error state. What am I doing wrong? Could anyone please give me some hints concerning USB DMA? Thanks a lot. I'll be back in January, after my Christmas vacation. I wish everybody a merry Christmas and a happy new year!
CC
One further question: is it enough to set up the dma in the SendDataToHost() metho for each packet which has to be send to the host? Or did I also specify the dma setup in the endpoint ISR (as shown in the code above)? Or do I have to send a ZLP packet each time the endpoint isr will be called (with packet-size zero)?
I've not to deal with ZLP and so on - the dma will everything do for me?
here's the whole code from the usb_reset() - I didn't change anyhting in this function...
void USB_Reset (void) { #if USB_DMA uint32_t n; #endif LPC_USB->USBEpInd = 0; LPC_USB->USBMaxPSize = USB_MAX_PACKET0; LPC_USB->USBEpInd = 1; LPC_USB->USBMaxPSize = USB_MAX_PACKET0; while ((LPC_USB->USBDevIntSt & EP_RLZED_INT) == 0); LPC_USB->USBEpIntClr = 0xFFFFFFFF; LPC_USB->USBEpIntEn = 0xFFFFFFFF ^ USB_DMA_EP; LPC_USB->USBDevIntClr = 0xFFFFFFFF; LPC_USB->USBDevIntEn = DEV_STAT_INT | EP_SLOW_INT | (USB_SOF_EVENT ? FRAME_INT : 0) | (USB_ERROR_EVENT ? ERR_INT : 0); #if USB_DMA LPC_USB->USBUDCAH = USB_RAM_ADR; LPC_USB->USBDMARClr = 0xFFFFFFFF; LPC_USB->USBEpDMADis = 0xFFFFFFFF; LPC_USB->USBEpDMAEn = USB_DMA_EP; LPC_USB->USBEoTIntClr = 0xFFFFFFFF; LPC_USB->USBNDDRIntClr = 0xFFFFFFFF; LPC_USB->USBSysErrIntClr = 0xFFFFFFFF; LPC_USB->USBDMAIntEn = 0x00000007; DDMemMap[0] = 0x00000000; DDMemMap[1] = 0x00000000; for (n = 0; n < USB_EP_NUM; n++) { udca[n] = 0; UDCA[n] = 0; } #endif }
The interrupt which occured after setting up the dma descriptor in the SendDataToHost method, is a "New DD Request Interrupt"... but after that nothing happens.
if (LPC_USB->USBDMAIntSt & 0x00000002) { /* New DD Request Interrupt */ { }
I hope you could give me some hints to get in working...
> After calling the method SendDataToHost(), the endpoint(in) will be executed only once.
Do you mean USB_EndPoint2() is called just once ? What is the "event" value on USB_EndPoint2() call?
USB_EndPoint2() should be called twice, - one for USB_EVT_IN_DMA_EOT, when the first transfer (300 bytes) finishes. - another for USB_EVT_IN_DMA_NDR, when the USB engine finds no DD chained after the first DD.
I said your code seems fine, but now I have doubt on this line in USB_EndPoint2()
if (event & (USB_EVT_IN_DMA_EOT) | (USB_EVT_IN_DMA_NDR))
Respond to just USB_EVT_IN_DMA_NDR, ignore USB_EVT_IN_DMA_EOT. > is it enough to set up the dma in the SendDataToHost() metho for each packet which has to be send to the host?
DMA automatically split the transfer into DD.MaxSize (64 bytes), when DD.BufLen is greater than DD.MaxSize. When the entire transfer of DD.BufLen completes, USB_EndPoint2() is called with USB_EVT_IN_DMA_EOT
> Or did I also specify the dma setup in the endpoint ISR (as shown in the code above)?
For single transfer, you don't need to touch to DMA in the endpoint ISR, at all. I thought you want to send the transfer repeatedly for test :-)
> Or do I have to send a ZLP packet each time the endpoint isr will be called (with packet-size zero)?
For CDC bulk IN endpoint, ZLP is required just when the transfer size is just the multiple of wMaxPacketSize (64 bytes). ie. 64, 128, 192, 256, ... As you send 300 bytes, no ZLP is required.
When ZLP is required, link another DMA Descriptor (DD) for ZLP after the first DD
Tsuneo
Hi Tsuneo,
thanks for your great response.
That means I can set up each dma transfer with my method SendDataToHost; and I didn't have to setup anything in the endpoint ISR. Because the packets which will be transmitted by the usb dma are not a continuous stream. That means packets are not always available, when the USB_EVT_IN_DMA_NDR event fires.
Or is the solution better to start the first dma transfer with the SendDataToHost function; and if another packet should be transfered or not will be determined in the endpoint isr
void USB_EndPoint2 (unsigned int event) { if (event & USB_EVT_IN_DMA_NDR) { /* End of Transfer or New Descriptor Request */ if(new_pkt_to_tx_is_avail == yes) { DD.BufAdr = (unsigned int)DataBuf + DataIn; /* DMA Buffer Address */ DD.BufLen = 300; /* DMA Packet-size */ DD.MaxSize = 64; /* 64Byte for bulk-transfer */ DD.Cfg.Val = 0; /* Initial DMA Configuration */ USB_DMA_Setup (CDC_DEP_IN, &DD); /* Setup DMA */ USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ //! I didn't need this command here, did I? LPC_USB->USBDMARSet |= 0x20; } else { DD.BufAdr = (unsigned int)DataBuf + DataIn; /* DMA Buffer Address */ DD.BufLen = 0; /* DMA Packet-size */ DD.MaxSize = 0; /* 64Byte for bulk-transfer */ DD.Cfg.Val = 0; /* Initial DMA Configuration */ USB_DMA_Setup (CDC_DEP_IN, &DD); /* Setup DMA */ USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ //! I didn't need this command here, did I? LPC_USB->USBDMARSet |= 0x20; } } }
voila it's WORKING Tsuneo.... I kicked all the stuff out of the endpoint isr and it works with 300Byte..
I made a small test example with 256 bytes so that I have to add a ZLP - but it still fails....I can't see anything with my software usb sniffer...
void SendDataToHost() { USB_DMA_DESCRIPTOR DD; DD.BufAdr = (unsigned int)pStartBufAddr; /* DMA Buffer Address */ DD.BufLen = 256; /* DMA Packet-size */ DD.MaxSize = 64; /* 64Byte for bulk-transfer */ //DD.Cfg.Type.IsoEP = 0; //DD.Cfg.Type.ATLE = 0; DD.Cfg.Val = 0; /* Initial DMA Configuration */ USB_DMA_Setup (CDC_DEP_IN, &DD); /* Setup DMA */ /* ************* ZLP **********/ DD.BufLen = 0; DD.MaxSize = 0; //DD.Cfg.Type.Link = 1; ???? DD.Cfg.Val = 0; USB_DMA_Setup (CDC_DEP_IN, &DD); USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ LPC_USB->USBDMARSet = 1 << EPAdr(CDC_DEP_IN); }
My endpoint ISR is still empty. It seems that the ZLP is not yet recognized.... What does the DD.Cfg.Type.Link stand for???
> The interrupt which occured after setting up the dma descriptor in the SendDataToHost method, is a "New DD Request Interrupt"... but after that nothing happens.
As you don't see USB_EVT_IN_DMA_EOT, something on the DD parameter should be wrong.
DD.BufAdr = (unsigned int)DataBuf + DataIn; /* DMA Buffer Address */
Is DataBuf[] sits on USB RAM (8K bytes), starting from 0x7FD0 0000 ? > Or is the solution better to start the first dma transfer with the SendDataToHost function; and if another packet should be transfered or not will be determined in the endpoint isr
In your first post, you said the firmware sends status info periodically to host. Then, the status info should be gathered in a timer ISR, or some periodical routine. SendDataToHost() is called from the same routine, too. USB_EndPoint2() is not touched, at all.
many thanks for your help!
Do you know how it would be possible to add a ZLP packet if I will send 256bytes for example? With the above code, it doesn't work (unfortunately).
a) using "DD.Cfg.Type.Link = 1;" for the second dma descriptor (ZLP)-> get two responses in the endpoint isr -> the status of the dma in the endpoint ISR is always "DMA Idle - Waiting for Trigger"
b) without "DD.Cfg.Type.Link = 1;" -> I will get two responses with the dma status "DMA Transfer Done (no Errors)" - but I didn't receive anything on the host...
In the setup of the DD for ZLP, this line is wrong.
DD.MaxSize = 0; // <---- 64
For ZLP, make just DD.BufLen to zero. Leave DD.MaxSize as same as wMaxPacketSize of the endpoint. When DD.MaxSize is zero, it means No_Packet DD. The EP will be NAKing.
"DD.Cfg.Type.Link = 1" must be, for chained DD.
unfortunately it seems to be very complicated to use ZLPs....
void SendDataToHost() { USB_DMA_DESCRIPTOR DD; DD.BufAdr = (unsigned int)pStartBufAddr; /* DMA Buffer Address */ DD.BufLen = 256; /* DMA Packet-size */ DD.MaxSize = 64; /* 64Byte for bulk-transfer */ //DD.Cfg.Type.IsoEP = 0; //DD.Cfg.Type.ATLE = 0; DD.Cfg.Val = 0; /* Initial DMA Configuration */ USB_DMA_Setup (CDC_DEP_IN, &DD); /* Setup DMA */ /* ************* ZLP **********/ DD.BufLen = 0; DD.MaxSize = 64; DD.Cfg.Val = 0; USB_DMA_Setup (CDC_DEP_IN, &DD); USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ LPC_USB->USBDMARSet = 1 << EPAdr(CDC_DEP_IN); }
With this code my usb device will send only 0-byte packets to the host; even if the host didn't open the serial port....
If I will call the SendDataToHost() method immediately after the last one, then I have to use Link = 1? How can I determine if the dma is still in progress with the last descriptor?
void test() { SendDataToHost(); //tx 400bytes SendDataToHost(); //tx 400bytes SendDataToHost(); //tx 400bytes } /* timer isr will be called every 20ms */ void Timer0_ISR() { test(); }
If I will do this, only 384-bytes will be transmitted instead of 400bytes. If set the "DD.Cfg.Type.Link = 0".
DMA Descriptors are managed in a linked list, for each endpoint. When USB_DMA_Setup() is called with "DD.Cfg.Type.Link = 1", on the same EP, new DD is chained after the last one.
void SendDataToHost() { USB_DMA_DESCRIPTOR DD; DD.BufAdr = (unsigned int)pStartBufAddr; /* DMA Buffer Address */ DD.BufLen = 256; /* DMA Packet-size */ DD.MaxSize = 64; /* 64Byte for bulk-transfer */ //DD.Cfg.Type.IsoEP = 0; //DD.Cfg.Type.ATLE = 0; DD.Cfg.Val = 0; /* Initial DMA Desc Configuration */ USB_DMA_Setup (CDC_DEP_IN, &DD); /* Register DD */ /* ************* ZLP **********/ DD.BufLen = 0; // Zero-Length DD.MaxSize = 64; DD.Cfg.Val = 0; // Clear config parameters DD.Cfg.Type.Link = 1; // link this DD after above first DD <-------- Add this line USB_DMA_Setup (CDC_DEP_IN, &DD); /* Register DD */ USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ LPC_USB->USBDMARSet = 1 << EPAdr(CDC_DEP_IN); }
> How can I determine if the dma is still in progress with the last descriptor?
Raise a flag in SendDataToHost(). In USB_EndPoint2() callback, catch (event == USB_EVT_IN_DMA_NDR) and drop the flag
do you know a suitable solution to determine the time when the host is ready to receive the first byte of data?
As well as, I'm searching for a good solution to switch between receive / and transmit data. The LPC processor is able to receive or to transmit data but both directions are not possilbe at the same time. Is it a good point to add a additional HID interface with one endpoint(in) so that I can change the direction?
> do you know a suitable solution to determine the time when the host is ready to receive the first byte of data?
PC application should actively notify the timing to the device. As you are working on CDC,
a) PC application sends a command (start / stop data streaming) over serial TX The device receives this command over the bulk OUT EP.
b) PC application enables / disables DTR The device receives Set_Control_Line_State request
Just after enumeration, usbser.sys (Windows CDC driver) drops DTR by Set_Control_Line_State CloseHandle() of the device handle also drops DTR by Set_Control_Line_State CreateFile() does nothing for DTR > The LPC processor is able to receive or to transmit data but both directions are not possilbe at the same time.
Endpoints run concurrently, time-shared in the split unit of transaction. Then, even whille your device sends long long data over the bulk IN EP, the bulk OUT EP can receive commands from PC application. LPC hardware also runs in this way, including DMA.
That means you will have to disable the usb interrupt, setting up the flag together with the usb dma descriptor in the SendDataToHost method. And easily drop the flag in the endpoint USB ISR. IS it not possible to miss an important usb interrupt during the settin up a new dma descriptor?
void SendDataToHost() { //set up dma descriptor //disable usb interrupts //if flag is set -> add Link = 1 USB_DMA_Setup (CDC_DEP_IN, &DD); //enable usb interrupts again /* and these two lines must only be called if the flag was not set... right? */ USB_DMA_Enable(CDC_DEP_IN); /* Enable DMA */ LPC_USB->USBDMARSet = 1 << EPAdr(CDC_DEP_IN); }
a) PC application sends a command (start / stop data streaming) over serial TX The device receives this command over the bulk OUT EP. Which command would that be - start transmitting/receiving?
::CreateFile() SetCommState (); Readfile() or Writefile() - is there a call to the usb device?
is it possible to get the number of bytes which were received by the last packet to determine if the packet was a normal data packet or a command packet? Is it also possible to use USB_ReadEP() because I will also use DMA for the BULK out endpoints?
void EndpointISR() { /* use of dma */ if(event & USB_EVT_OUT_DMA_EOT) { /* End of Transfer */ if (USB_DMA_BufAdr(CDC_DEP_OUT) != ((unsigned int)DataBuf0 + DataIn0)) { /* Data Available */ /* determine if the rxd data is a cmd or a dmx data pkt */ /* is it possible to get the size of the rxd packet to determine if the packet is a data packet or a command packet? */ if(command_pkt) { USB_ReadEP(CDC_DEP_OUT, &CommandReq[0]); SetupCommand(&CommandReq[0]); return; } } } if (event & (USB_EVT_OUT_DMA_EOT) | (USB_EVT_OUT_DMA_NDR)) { /* End of Transfer or New Descriptor Request */ DD.BufAdr = (unsigned int)DataBuf0 + DataIn0; DD.BufLen = DATA_TX_PKT_LEN; DD.MaxSize = 64; /* 64Byte for bulk-transfer */ DD.Cfg.Val = 0; /* Initial DMA Configuration */ USB_DMA_Setup (CDC_DEP_OUT, &DD); /* Setup DMA */ USB_DMA_Enable(CDC_DEP_OUT); } /* 2) */ if(event & USB_EVT_IN_DMA_NDR) dmaInProgress &= ~(1 << EPAdr(CDC_DEP_IN)); }