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.

Tagc
  • 8,736
  • 7
  • 61
  • 114

1 Answers1

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.

Turbo J
  • 7,563
  • 1
  • 23
  • 43
  • The documentation actually states that it is aligned to a 128-byte boundary – Hasturkun Aug 21 '14 at 14:20
  • Thanks for the reply. It's 128 bytes, not 256. I think I'm doing that alright since I'm following the example code, but I'll update my post with what I'm doing in a moment. – Tagc Aug 21 '14 at 14:20
  • I'm also using the IAR compiler, sorry. So no GCC issues. – Tagc Aug 21 '14 at 14:29
  • @Turbo J, actually you might be on to something. When I initialise my USB peripheral, I'm supposed to zero all the data in the UDCA. However, the data at the start of the USB RAM block (0x20000000 I think) isn't getting zero'd. So you might be right. I'll look into it a bit more. – Tagc Aug 21 '14 at 14:37
  • Well this is interesting. First off, I'm tired and I should have said USB RAM appears to start at address 0x2008C000. Either way, it looks like the `UDCA` is being created in the same location as all the other variables. It seems to be being created between addresses 0x10009CA8 and 0x10009D27. I guess the pragma directive isn't working as intended. This is certainly starting to look like the cause of the problem I'm having, but I wonder why the pragma directive isn't working. To tell the truth, I just copied and pasted it from the example code and don't really understand it. – Tagc Aug 21 '14 at 15:04
  • Thouse `#pragma` directives are not standard `C`. Consult your compiler/linker documentation on how to treat variables so that they will end at specific locations in RAM. – Turbo J Aug 21 '14 at 15:22
  • I analysed my program's outputted ELF file with `ielfdumparm.exe`. I found out that there basically isn't any section called "USB_RAM" so I have absolutely no idea why `#pragma location="USB_RAM"` is in the example code. The only possibility is that it's defined somewhere in the example code where I'm not seeing it. I tried using `#pragma location=0x2008C000` instead, since 0x2008C000 is where USB RAM is supposed to begin according to the documentation. This places the UDCA where I expect it to, but it causes my program to crash when it runs, so I guess that's not where I should put it. – Tagc Aug 21 '14 at 15:56
  • (Cont.) What I want to know is where the people writing the example code were imagining "USB_RAM" to be. I'd have thought it would be 0x2008C000 but yeah - causes the program to crash. I'll have to have a look at it again tomorrow. Thanks for your help. – Tagc Aug 21 '14 at 15:58
  • Using #pragma location=0x20000000UL fixed the AHB bus problem. Now instead of getting an AHB bus error, I get a New Device Descriptor Request as I expect. The DMA isn't working perfectly yet but my current issue is resolved so I'm marking this as the accepted answer. – Tagc Aug 22 '14 at 07:17