3

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...

fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • This looks relevant: https://community.st.com/s/question/0D50X00009XkdeRSAR/how-is-pmaadress-calculated-in-halpcdexpmaconfig – fadedbee Jan 22 '20 at 11:51

2 Answers2

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.

A.K.
  • 839
  • 6
  • 13
  • I think you may be on the right track, as I haven't encountered EP_NUM yet. I can't find it with `grep -R "EP_NUM" *` in the project directory. (I have seen that various loops over endpoints use `for (i = 0U; i < hpcd->Init.dev_endpoints; i++)`, but `dev_endpoints` is initialised to `8` in `usbd_conf.c`.) – fadedbee Jan 29 '20 at 10:50
  • Thanks for all your help. This was exactly correct. (I also pushed EP3's buffer up from `0x100` to `0x128`, to be consistent.) – fadedbee Jan 30 '20 at 09:06
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()?

fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • I've just found: https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf which seems to contain all the information that was missing from the individual chip datasheets. – fadedbee Jan 28 '20 at 15:00
  • 1
    Why is rxaddrs[0] 0x18? This seems wrong. Assuming BTABLE is at the beginning of the PMA, then there is EP0 RX buffer, then EP0 TX buffer (at 0x58 = 0x18 + 64 - ok), this means Cube only allocates buffers space for *3* endpoints. The value 0 may be written as a part of USB reception because it is the same area as RX buffer for EP 0. – A.K. Jan 29 '20 at 03:56
  • 1
    What is the value of EP_NUM? It should be at least 4 if you use EP 3. – A.K. Jan 29 '20 at 03:58
  • I can't find it with grep -R "EP_NUM" * in the project directory. – fadedbee Jan 29 '20 at 10:59
  • Re: rxaddrs[0] - Is `0x18` 24 addresses into the 16-bit PMA or `0x18` bytes / 12 addresses into the PMA? If it's bytes, then received packets on EP0 would be written into `pma[12]` overwriting the TX buffer address for EP3. – fadedbee Jan 29 '20 at 11:10
  • 1
    Your pma[2] is 0x18. This is byte offset into the PMA, as the hardware sees it, specifying the RX buffer location for EP0. Yes, this is what likely happens. – A.K. Jan 29 '20 at 14:01
  • 1
    I'm not sure where it is set in the generated code, but in my ST USB examples, EP_NUM is defined in usb_conf.h, along with the BTABLE value and endpoint numbers, etc. – A.K. Jan 29 '20 at 14:06
  • 1
    Actually, I see now that EP0 offsets in the buffers are hardcoded there too. I thought the code does it dynamically. I will add my settings to the answer as an example. – A.K. Jan 29 '20 at 14:07
  • Re: "Your pma[2] is 0x18." Yes, I think we now know why the EP3 TX buffer address is being overwritten. – fadedbee Jan 29 '20 at 15:16
  • Are you using CubeMX/CubeIDE? The string `EP_NUM` does not appear in the project. The contents of the BTABLE "miraculously" appears when the USB clock is started. I cannot find what code writes it, nor where the values come from. – fadedbee Jan 29 '20 at 15:16
  • 1
    I am spared from CubeMX. I have various pieces of it though. The content of BTABLE miraculously appears because it is stored in RAM that preserves its content over resets. – A.K. Jan 29 '20 at 15:21
  • 1
    Isn't this from the code you posted: HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18); HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58); – A.K. Jan 29 '20 at 15:25
  • No, `HAL_PCDEx_PMAConfig()` initialises values in `hcpd`, `hcpd->IN_ep[]` and `hcpd->OUT_ep[]`. It doesn't (directly) write to the BTABLE, or call any other function. Those `hcpd` values are used when data is written into TX buffers in the PMA, or read from RX buffers, but not written into the BTABLE directly. – fadedbee Jan 29 '20 at 15:34
  • Re: "RAM that preserves its content over resets" - I zero the PMA at the start of `main()`. It must come from somewhere else. – fadedbee Jan 29 '20 at 15:36
  • 1
    Do you zero it before clock is enabled? – A.K. Jan 29 '20 at 15:59
  • 1
    In my copy of F3 Cube, BTABLE address is set in HAL_PCD_EP_Open() using the PCD_SET_EP_TX_ADDRESS() macro. – A.K. Jan 29 '20 at 16:07
  • 1
    It's in stm32f3xx_hal_pcd.c – A.K. Jan 29 '20 at 16:07
  • Yes, I zero it before the USB clock enable macro. It is at that point that the content of the BTABLE in PMA becomes non-zero. – fadedbee Jan 29 '20 at 16:11
  • `HAL_PCD_EP_Open()` calls `USB_ActivateEndpoint()` which uses the `PCD_SET_EP_TX_ADDRESS()` macro, but I only saw this called for EP0 (when I added a breakpoint). I will check this again tomorrow. – fadedbee Jan 29 '20 at 16:14
  • 1
    Writing to USB RAM while it has no running clock does nothing. – A.K. Jan 29 '20 at 16:15
  • 1
    In essence :-) you need to change 0x18 to 0x40 and 0x58 to 0x80, and make sure other buffers do not overlap too. Eventually these values propagate to BTABLE in some way or another. – A.K. Jan 29 '20 at 16:18
  • Thanks, I will change the buffers tomorrow. I did not expect "Writing to USB RAM while it has no running clock does nothing.". – fadedbee Jan 29 '20 at 16:24