How can I use the USB DMA Engine for the LPC1788?

2

I'm developing code for the NXP LPC1788 microcontroller and lately I've been trying to improve the way that the USB works. My current issue with the USB is that it's configured in slave mode and, due to the high volume of messages that have to be sent and received in normal operation, the CPU spends most of its time handling USB which creates a bottleneck.

I've been trying to resolve this issue by switching from slave-mode configuration to DMA-mode. I've been using example projects to help and I think most of the code I need is in place.

USB initialisation works fine, as before. The problem is that as soon as I try to send a USB message to an endpoint that I configure in DMA-mode (by enabling the appropriate bit in the EpDMAEn register), I get a USB System Error Interrupt for that endpoint. The only information I can get about this is that:

If a system error (AHB bus error) occurs when transferring the data or when fetching or updating the DD the corresponding bit is set in this register. SysErrIntSt is a read-only register.

At this point in the program, I haven't touched the UDCA or setup any DMA descriptors or anything like that past initialisation. This is an error that occurs as soon as the first message is received on the USB bus before I need to do any of that, I believe.

I'm using endpoints 2 IN and OUT, which are double-buffered bulk endpoints with a maximum packet size of 64 bytes.

I've confirmed that the USB works fine if I don't use the DMA.

I've confirmed that the USB works fine if I go through the process of initialising the DMA engine but configure the endpoint in slave-mode instead of DMA-mode.

I've confirmed that the USB Mass Storage example under Example Projects -> NXP -> LP17xx -> 177x_8x CMSIS works fine if I use its default configuration:

...
#define USB_DMA             1
#define USB_DMA_EP          0x00000000
...

but also breaks in the same way if I change this to:

...
#define USB_DMA             1
#define USB_DMA_EP          0x00000030 /* Endpoint 2 IN and OUT */
...

At the beginning of the USB hardware source file, I put the following:

#ifdef USB_DMA

// Stores information received using DMA on OUT endpoints.
//uint8_t dataBufferOUT[DD_BUFFER_SIZE*MAX_PACKET_SIZE];
uint8_t *dataBufferOUT = (uint8_t*)DMA_BUF_ADR;

// Stores information transferred using DMA on IN endpoints.
//uint8_t dataBufferIN[DD_BUFFER_SIZE*MAX_PACKET_SIZE];
uint8_t *dataBufferIN = (uint8_t*)(DMA_BUF_ADR+DD_BUFFER_SIZE*
                                   USB_MAX_PACKET_SIZE);

// Current dataBufferOUT index;
uint16_t dataOUT;

// Current dataBufferIN index.
uint16_t dataIN;

#if defined (__CC_ARM)
#pragma arm section zidata = "USB_RAM"
uint32_t UDCA[USB_EP_NUM];                     /* UDCA in USB RAM */

uint32_t DD_NISO_Mem[4*DD_NISO_CNT];           /* Non-Iso DMA Descriptor Memory */
uint32_t DD_ISO_Mem [5*DD_ISO_CNT];            /* Iso DMA Descriptor Memory */
#pragma arm section zidata
#elif defined ( __ICCARM__ )
#pragma location = "USB_RAM"
uint32_t UDCA[USB_EP_NUM];                     /* UDCA in USB RAM */
#pragma location = "USB_RAM"
uint32_t DD_NISO_Mem[4*DD_NISO_CNT];           /* Non-Iso DMA Descriptor Memory */
#pragma location = "USB_RAM"
uint32_t DD_ISO_Mem [5*DD_ISO_CNT];            /* Iso DMA Descriptor Memory */

#else
uint32_t UDCA[USB_EP_NUM]__attribute__((section ("USB_RAM")));                     /* UDCA in USB RAM */

uint32_t DD_NISO_Mem[4*DD_NISO_CNT]__attribute__((section ("USB_RAM")));           /* Non-Iso DMA Descriptor Memory */
uint32_t DD_ISO_Mem [5*DD_ISO_CNT]__attribute__((section ("USB_RAM")));            /* Iso DMA Descriptor Memory */
#endif /*__GNUC__*/
uint32_t udca[USB_EP_NUM];                     /* UDCA saved values */

uint32_t DDMemMap[2];                          /* DMA Descriptor Memory Usage */

#endif

I initialise the USB peripheral with this code:

void USBInit()
{  
  // Configure USB pins.
  PINSEL_ConfigPin(0, 29, 1); // USB_D+1
  PINSEL_ConfigPin(0, 30, 1); // USB_D-1
  PINSEL_ConfigPin(1, 18, 1); // USB_UP_LED1
  PINSEL_ConfigPin(2,  9, 1); // USB_CONNECT1
  PINSEL_ConfigPin(1, 30, 2); // USB_VBUS 

  // Turn on power and clock
  CLKPWR_ConfigPPWR(CLKPWR_PCONP_PCUSB, ENABLE);

  //PINSEL_SetPinMode(1, 30, PINSEL_BASICMODE_PLAINOUT);

  // Set DEV_CLK_EN and AHB_CLK_EN.
  LPC_USB->USBClkCtrl |= 0x12;

  // Wait until change is reflected in clock status register.
  while((LPC_USB->USBClkSt & 0x12) != 0x12);

  // Enable NVIC USB interrupts.
  NVIC_EnableIRQ(USB_IRQn);

  // Reset the USB.
  USBReset();

  // Set device address to 0x0 and enable device & connection.
  USBSetAddress(0);

  // TEMP.
  sendMessageFlag = 0;

#ifdef USB_DMA
  dataIN = 0;
  dataOUT = 0;
#endif
}

My USB reset code:

void USBReset()
{
  LPC_USB->EpInd = 0;
  LPC_USB->MaxPSize = USB_MAX_PACKET_SIZE;
  LPC_USB->EpInd = 1;
  LPC_USB->MaxPSize = USB_MAX_PACKET_SIZE;
  while ((LPC_USB->DevIntSt & EP_RLZED_INT) == 0);

  LPC_USB->EpIntClr  = 0xFFFFFFFF;

#ifdef USB_DMA
  LPC_USB->EpIntEn   = 0xFFFFFFFF ^ USB_DMA_EP;
#else
  LPC_USB->EpIntEn   = 0xFFFFFFFF;
#endif

  LPC_USB->DevIntClr = 0xFFFFFFFF;
  LPC_USB->DevIntEn  = DEV_STAT_INT | EP_SLOW_INT /*| EP_FAST_INT*/ ;

#ifdef USB_DMA
  uint32_t n;

  LPC_USB->UDCAH   = USB_RAM_ADR;
  LPC_USB->DMARClr = 0xFFFFFFFF;
  LPC_USB->EpDMADis  = 0xFFFFFFFF;
  LPC_USB->EpDMAEn   = USB_DMA_EP;
  LPC_USB->EoTIntClr = 0xFFFFFFFF;
  LPC_USB->NDDRIntClr = 0xFFFFFFFF;
  LPC_USB->SysErrIntClr = 0xFFFFFFFF;
  LPC_USB->DMAIntEn  = 0x00000007;
  DDMemMap[0] = 0x00000000;
  DDMemMap[1] = 0x00000000;
  for (n = 0; n < USB_EP_NUM; n++) {
    udca[n] = 0;
    UDCA[n] = 0;
  }
#endif
}

When ready, this is used to run the USB:

void USBRun()
{
  USBSetConnection(TRUE);
}

Finally, my USB interrupt routine:

void USB_IRQHandler(void)
{
  OS_EnterInterrupt();

  uint32_t data, val, pIndex, lIndex, currEpisr;
  uint32_t interruptData = LPC_USB->DevIntSt;
#ifdef USB_DMA
  uint32_t dmaInterruptData = LPC_USB->DMAIntSt; 
#endif

  //printf("InterruptData: 0x%x\n", interruptData);

  if (interruptData & ERR_INT)
  {
    writeSIECommand(CMD_RD_ERR_STAT);
    data = readSIECommandData(DAT_RD_ERR_STAT);
   // printf("Error data: 0x%x\n", data);
    //getchar();
  }

  // Handle device status interrupt (reset, connection change, suspend/resume).
  if(interruptData & DEV_STAT_INT)
  {
    LPC_USB->DevIntClr = DEV_STAT_INT;
    writeSIECommand(CMD_GET_DEV_STAT);
    data = readSIECommandData(DAT_GET_DEV_STAT);
    //printf("Data: 0x%x\n", data);

    // Device reset.
    if(data & DEV_RST)
    {
      USBReset();
      USBResetCore();
      //printf("USB Reset\n");
    }

    // Connection change.
    if(data & DEV_CON_CH)
    {
      //printf("Connection change\n");
      /* Pass */
    }

    // Suspend/resume.
    if(data & DEV_SUS_CH)
    {
      if(data & DEV_SUS)
      {
        //printf("USB Suspend\n");
        USBSuspend();
      }
      else
      {
        //printf("USB Resume\n");
        USBResume();
      }
    }

    OS_LeaveInterrupt();
    return;
  }

  // Handle endpoint interrupt.
  if(interruptData & EP_SLOW_INT)
  {  
    //printf("Endpoint slow\n");
    data = LPC_USB->EpIntSt;
    //printf("EP interrupt: 0x%x\n", data);

    currEpisr = 0;

    for(pIndex=0; pIndex < USB_EP_NUM; pIndex++)
    {
      lIndex = pIndex >> 1;

      if(data == currEpisr) break;
      if(data & (1 << pIndex))
      {
        currEpisr |= (1 << pIndex);

        LPC_USB->EpIntClr = 1 << pIndex;
        while((LPC_USB->DevIntSt & CDFULL_INT) == 0);
        val = LPC_USB->CmdData;

        // OUT endpoint.
        if((pIndex & 1) == 0)
        {
          // Control OUT endpoint.
          if(pIndex == 0)
          {
            // Setup Packet.
            if(val & EP_SEL_STP)
            {
              if(USB_P_EP[0])
              {
                USB_P_EP[0](USB_EVT_SETUP);
                continue;
              }
            }
          }

          if(USB_P_EP[lIndex])
          {
            USB_P_EP[lIndex](USB_EVT_OUT);
          }
        }

        // IN endpoint.
        else
        {
          if(USB_P_EP[lIndex])
          {
            if(lIndex > 0) clearSendMessageFlag(lIndex);
            USB_P_EP[lIndex](USB_EVT_IN);
          }
        }
      }
    }

    LPC_USB->DevIntClr = EP_SLOW_INT;
  }

#ifdef USB_DMA

  if (dmaInterruptData & 0x00000001) {          /* End of Transfer Interrupt */
    data = LPC_USB->EoTIntSt;
    for (pIndex = 2; pIndex < USB_EP_NUM; pIndex++) {      /* Check All Endpoints */
      if (data & (1 << pIndex)) {
        lIndex = pIndex >> 1;
        if ((pIndex & 1) == 0) {                 /* OUT Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_OUT_DMA_EOT);
          }
        } else {                            /* IN Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_IN_DMA_EOT);
          }
        }
      }
    }
    LPC_USB->EoTIntClr = data;
  }

  if (dmaInterruptData & 0x00000002) {          /* New DD Request Interrupt */
    data = LPC_USB->NDDRIntSt;
    for (pIndex = 2; pIndex < USB_EP_NUM; pIndex++) {      /* Check All Endpoints */
      if (data & (1 << pIndex)) {
        lIndex = pIndex >> 1;
        if ((pIndex & 1) == 0) {                 /* OUT Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_OUT_DMA_NDR);
          }
        } else {                            /* IN Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_IN_DMA_NDR);
          }
        }
      }
    }
    LPC_USB->NDDRIntClr = data;
  }

  if (dmaInterruptData & 0x00000004) {          /* System Error Interrupt */
    data = LPC_USB->SysErrIntSt;
    for (pIndex = 2; pIndex < USB_EP_NUM; pIndex++) {      /* Check All Endpoints */
      if (data & (1 << pIndex)) {
        lIndex = pIndex >> 1;
        if ((pIndex & 1) == 0) {                 /* OUT Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_OUT_DMA_ERR);
          }
        } else {                            /* IN Endpoint */
          if (USB_P_EP[lIndex]) {
            USB_P_EP[lIndex](USB_EVT_IN_DMA_ERR);
          }
        }
      }
    }
    LPC_USB->SysErrIntClr = data;
  }

#endif /* USB_DMA */

  OS_LeaveInterrupt();
}

What I'm ideally looking for is a solution if you can spot an error in any of my code or a working example program that can be run on an LPC1788 that demonstrates USB message transmission and reception using the DMA engine.

I'd also appreciate any information on what can cause an AHB bus error.

EDIT

In response to Turbo J's answer below:

Check the address of UDCA. The required alignment is very strict, 256 byte IIRC, so the address must end with 0x00 as LDB. GCC requires support for the USB_RAM section in the linker script.

In my USB hardware header file, I have:

/* USB RAM Definitions */
#define USB_RAM_ADR     LPC_PERI_RAM_BASE  /* USB RAM Start Address */
#define USB_RAM_SZ      0x00004000  /* USB RAM Size (4kB) */

LPC_PERI_RAM_BASE has the value 0x20000000UL.

In my source file, I have:

#if defined (__CC_ARM)
#pragma arm section zidata = "USB_RAM"
uint32_t UDCA[USB_EP_NUM];                     /* UDCA in USB RAM */

uint32_t DD_NISO_Mem[4*DD_NISO_CNT];           /* Non-Iso DMA Descriptor Memory */
uint32_t DD_ISO_Mem [5*DD_ISO_CNT];            /* Iso DMA Descriptor Memory */
#pragma arm section zidata
#elif defined ( __ICCARM__ )
#pragma location = "USB_RAM"
uint32_t UDCA[USB_EP_NUM];                     /* UDCA in USB RAM */
#pragma location = "USB_RAM"
uint32_t DD_NISO_Mem[4*DD_NISO_CNT];           /* Non-Iso DMA Descriptor Memory */
#pragma location = "USB_RAM"
uint32_t DD_ISO_Mem [5*DD_ISO_CNT];            /* Iso DMA Descriptor Memory */

#else
uint32_t UDCA[USB_EP_NUM]__attribute__((section ("USB_RAM")));                     /* UDCA in USB RAM */

uint32_t DD_NISO_Mem[4*DD_NISO_CNT]__attribute__((section ("USB_RAM")));           /* Non-Iso DMA Descriptor Memory */
uint32_t DD_ISO_Mem [5*DD_ISO_CNT]__attribute__((section ("USB_RAM")));            /* Iso DMA Descriptor Memory */
#endif /*__GNUC__*/
uint32_t udca[USB_EP_NUM];                     /* UDCA saved values */

uint32_t DDMemMap[2];                          /* DMA Descriptor Memory Usage */

#endif

Where USB_EP_NUM is 32.

Therefore, UDCA should be a 128-byte array that begins at the start of the RAM memory block, I believe.

c
usb
microcontroller
lpc
asked on Stack Overflow Aug 21, 2014 by Tagc • edited Aug 21, 2014 by Tagc

1 Answer

2

Check the address of UDCA. The required alignment is very strict, 256 byte IIRC, so the address must end with 0x00 as LDB. GCC requires support for the USB_RAM section in the linker script.

answered on Stack Overflow Aug 21, 2014 by Turbo J

User contributions licensed under CC BY-SA 3.0