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_report
s 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: 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
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...
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 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()?
User contributions licensed under CC BY-SA 3.0