CubeMX-generated USB HID device sends wrong data when both endpoint and PMA address are changed

2

I'm debugging a problem with a composite device that I'm creating, and have recreated the issue in freshly-CubeMX-generated HID-only code, to make it easier to resolve.

I've added small amount of code to main() to let me send USB HID mouse-clicks, and flash an LED, when the blue-button is pressed.

...
uint8_t click_report[CLICK_REPORT_SIZE] = {0};
extern USBD_HandleTypeDef hUsbDeviceFS;
...
int main(void)
{
  ...
  while (1)
  {
      /* USER CODE END WHILE */

      /* USER CODE BEGIN 3 */
      if(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_SET){
          HAL_GPIO_WritePin(LD4_GPIO_Port, LD4_Pin, GPIO_PIN_SET);

          click_report[0] = 1; // send button press
          USBD_HID_SendReport(&hUsbDeviceFS, click_report, CLICK_REPORT_SIZE);
          HAL_Delay(50);

          click_report[0] = 0; // send button release
          USBD_HID_SendReport(&hUsbDeviceFS, click_report, CLICK_REPORT_SIZE);

          HAL_Delay(200);

          HAL_GPIO_WritePin(LD4_GPIO_Port, LD4_Pin, GPIO_PIN_RESET);
      }
  }

I am using Wireshark and usbmon (on Ubuntu 16.04) to look at the packets which my STM32F3DISCOVERY board sends.

With this freshly-generated code, I can see URB_INTERRUPT packets being sent from 3.23.1. (Only the last part of that address, the endpoint, is relevant.)

The packet contents are:

01 00 00 00
00
00 00 00 00
00

as expected.

(The 5-byte click_reports are fragmented into 4-byte and 1-byte messages, as there is a 4-byte maximum packet size for HID.)

I then changed HID_EPIN_ADDR in usdb_hid.h from 0x81 to 0x83, to make the device use endpoint 3 for HID messages, instead of endpoint 1.

//#define HID_EPIN_ADDR                 0x81U
#define HID_EPIN_ADDR                 0x83U

With this change, everything continued to work, with the expected change that packets are being sent from x.x.3. The packets still contain:

01 00 00 00
00
00 00 00 00
00

As far as I can see, this should not work, as I haven't yet allocated an address for endpoint 3 (0x83) in the PMA (packet memory area).

I do this, by editing usb_conf.c:

  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  //HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0x100);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x180);
  /* USER CODE END EndPoint_Configuration_HID */
  return USBD_OK;
}

Now, when I send the same 01 00 00 00 00 and 00 00 00 00 00 click_reports I see packet contents of:

58 00 2c 00
58
58 00 2c 00
58

I have traced the contents of the non-PMA buffer right down to USB_WritePMA in stm32f3xx_ll_usb.

The sending code (in stm32f3xx_ll_usb) is:

  /* IN endpoint */
  if (ep->is_in == 1U)
  {
    /*Multi packet transfer*/
    if (ep->xfer_len > ep->maxpacket)
    {
      len = ep->maxpacket;
      ep->xfer_len -= len;
    }
    else
    {
      len = ep->xfer_len;
      ep->xfer_len = 0U;
    }

    /* configure and validate Tx endpoint */
    if (ep->doublebuffer == 0U)
    {
      USB_WritePMA(USBx, ep->xfer_buff, ep->pmaadress, (uint16_t)len);
      PCD_SET_EP_TX_CNT(USBx, ep->num, len);
    }
    else
    {

Why is the data on the wire not the data that I give USB_WritePMA, once I've added HAL_PCDEx_PMAConfig(... for endpoint address 0x83?


Update:

If I change usb_conf.c to let endpoint address 0x83 use the PMA address that is normally used by 0x81:

  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  //HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0x100);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x100);
  /* USER CODE END EndPoint_Configuration_HID */

the packets on the wire are still corrupted:

58 00 2c 00
58
58 00 2c 00
58

If I return usb_conf.c to its initial, generated, state (where 0x83 has no PMA address, and 0x81 uses 0x100):

  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0x100);
  //HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x100);
  /* USER CODE END EndPoint_Configuration_HID */

the output works as expected:

01 00 00 00
00
00 00 00 00
00

Update 2:

I added a break-point in USB_ActivateEndpoint() in stm32f3xx_ll_usb.c.

Surprisingly this is only ever called for endpoint 0.

Therefore, the ep->pmaadress (sic) is never "written into hardware", and only used in higher-level code.

This must mean that the values of pmaadress for the endpoints are set to some default value, and I do not know the default value for endpoint 0x83 and so can't set it.

When I return to work on Friday, I will add debugging to read-out the default values. If they do not exist, I will be very confused.


Update 3:

I added the following debugging:

uint16_t *tx_addr_ptr(USB_TypeDef *USBx, uint8_t ep_num) {
  register uint16_t *_wRegValPtr;
  register uint32_t _wRegBase = (uint32_t)USBx;
  _wRegBase += (uint32_t)(USBx)->BTABLE;
  _wRegValPtr = (uint16_t *)(_wRegBase + 0x400U + (((uint32_t)(ep_num) * 8U) * 2U));
  return _wRegValPtr;
}

uint16_t *rx_addr_ptr(USB_TypeDef *USBx, uint8_t ep_num) {
  register uint16_t *_wRegValPtr;
  register uint32_t _wRegBase = (uint32_t)USBx;
  _wRegBase += (uint32_t)(USBx)->BTABLE;
  _wRegValPtr = (uint16_t *)(_wRegBase + 0x400U + ((((uint32_t)(ep_num) * 8U) + 4U) * 2U));
  return _wRegValPtr;
}
...
HAL_StatusTypeDef USB_ActivateEndpoint(USB_TypeDef *USBx, USB_EPTypeDef *ep)
{
  ...
  int txaddrs[8] = {0};
  int rxaddrs[8] = {0};
  for (int i = 0; i < 8; ++i) {
    txaddrs[i] = *tx_addr_ptr(USBx, i);
    rxaddrs[i] = *rx_addr_ptr(USBx, i);
  }

This showed me the following values (in the debugger):

txaddrs:
  0: 0x58
  1: 0xf5c4
  2: 0xc1c2
  3: 0x100

rxaddrs:
  0: 0x18
  1: 0xfa9b
  2: 0xcb56
  3: 0x0

These, unexpectedly, look correct.

0x100 is the txaddr of endpoint 3, even though USB_ActivateEndpoint() has only just been called for the first time.

With a lot of grepping, I found that PCD_SET_EP_TX_ADDRESS (in stm32f3xx_hal_pcd.h) is not only used directly in USB_ActivateEndpoint(), but also in the PCD_SET_EP_DBUF0_ADDR macro from `stm32f3xx_hal_pcd.h.

PCD_SET_EP_DBUF0_ADDR does not appear to be used, so I do not know how the (changed) values from usbd_conf.c:

USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
  ...
  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  //HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0x100);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x100);
  /* USER CODE END EndPoint_Configuration_HID */

get into the memory-mapped USB registers.

I can infer, from the presence of a 0x00 in rxaddr[3] (endpoint 3) that they happen in pairs (as there is no call to HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x3 , PCD_SNG_BUF, 0x0);).


Update 4:

After changing the device to use endpoint 1 again, the value of 0x100 in txaddrs[3] remained. It was simply there from the last run, which removes a little confusion.


Update 5:

It's a BTABLE problem. The BTABLE register has a value of 0x00, putting the btable at the start of the PMA.

The PMA looks like this: BTABLE and the start of the PMA is the btable.

I found:

PMAAddr + BASEADDR_BTABLE + 0x00000000 : EP0_TX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x00000002 : EP0_TX_COUNT
PMAAddr + BASEADDR_BTABLE + 0x00000004 : EP0_RX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x00000006 : EP0_RX_COUNT
PMAAddr + BASEADDR_BTABLE + 0x00000008 : EP1_TX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x0000000A : EP1_TX_COUNT
PMAAddr + BASEADDR_BTABLE + 0x0000000C : EP1_RX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x0000000E : EP1_RX_COUNT
PMAAddr + BASEADDR_BTABLE + 0x00000010 : EP2_TX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x00000012 : EP2_TX_COUNT
PMAAddr + BASEADDR_BTABLE + 0x00000014 : EP2_RX_ADDR
PMAAddr + BASEADDR_BTABLE + 0x00000016 : EP2_RX_COUNT

on https://community.st.com/s/question/0D50X00009XkaUASAZ/stm32-usb-endpoint-configuration-clarification-questions

This shows that endpoints 0x81 and 0x82 work because both pma[4] and pma[8] are set to 0x100.

Endpoint 0x83 does not work because pma[12] is set to 0x0.

This is consistent with the corrupted data having the value 58 00 2c 00 - the USB hardware was reading pma[12] and therefore sending the uint16_t's from pma[0], which are 0x0058 0x002c, sent reversed because of little-endianness. (Note: the PMA is only 16-bits wide, so there are only two bytes at each address here.)

The call to HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x82, PCD_SNG_BUF, 0x100); does not set up the btable pointer at pma[12], it just notes that PMA address to copy-to.

Now I just need to find where the content of the btable is being written...

usb
stm32
cubemx
stm32f3
asked on Stack Overflow Jan 22, 2020 by fadedbee • edited Jan 27, 2020 by fadedbee

2 Answers

1

The TX address of EP3 is being overwritten by an incoming USB packet because it is located at the same offset in PMA as the RX buffer for the control EP0. The original code works okay because it only uses EP1.

How exactly these offsets are set depends on what's in the layers of STMCube, and my copy seems to be different, but this appears where the offsets of RX and TX buffers in EP0 are set in the OP's code:

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);

These constants need to be changed to 0x40 and 0x80 (for example).

In my version, these offsets are defined in a header file and there is also EP_NUM constant, but how it's used is unclear.

Everything else seems to be just distractions.

answered on Stack Overflow Jan 29, 2020 by A.K. • edited Jan 29, 2020 by A.K.
0

A work-around is to add the two lines following // correct PMA BTABLE to HAL_PCD_EP_Transmit() in stm32f3xx_hal_pcd.c:

HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
{
  PCD_EPTypeDef *ep;

  ep = &hpcd->IN_ep[ep_addr & EP_ADDR_MSK];

  /*setup and start the Xfer */
  ep->xfer_buff = pBuf;
  ep->xfer_len = len;
  ep->xfer_count = 0U;
  ep->is_in = 1U;
  ep->num = ep_addr & EP_ADDR_MSK;

  // correct PMA BTABLE
  uint32_t *btable = (uint32_t *) USB_PMAADDR;
  btable[ep->num * 4] = ep->pmaadress;
  ...

This causes a correction to the location of endpoint 3's TX buffer before every write. This is wasteful, but it was not sufficient to set it once, as the value in pma[12] was being overwritten.

I have used this workaround in successfully creating a composite CDC (serial) and HID device.

To solve this properly, I need an answer to: What initialises the contents of the STM32's USB BTABLE when the __HAL_RCC_USB_CLK_ENABLE() macro is executed in HAL_PCD_MspInit()?

answered on Stack Overflow Jan 28, 2020 by fadedbee • edited Jan 28, 2020 by fadedbee

User contributions licensed under CC BY-SA 3.0