RawHID recv and send timeout in microseconds

I use RawHID with a Teensy 4.1 in my project and I need a control loop that run at at least 1kHz. Since RawHID's timeout are measured in ms, it is impossible to have a higher frequency than 500Hz since I call the recv and send function in each loop and they each take at least 1 ms.

To solve my problem, I went to the source code, in the cores/teensy4/usb_rawhid.c file and change the following :

Code:
int usb_rawhid_recv(void *buffer, uint32_t timeout)
{
	uint32_t wait_begin_at = systick_millis_count;
	uint32_t tail = rx_tail;
	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		if (tail != rx_head) break;
		if (systick_millis_count - wait_begin_at > timeout)  {
			return 0;
		}
		yield();
	}
//	digitalWriteFast(0, LOW);
	if (++tail > RX_NUM) tail = 0;
	uint32_t i = rx_list[tail];
	rx_tail = tail;

	memcpy(buffer,  rx_buffer + i * RAWHID_RX_SIZE, RAWHID_RX_SIZE);
	rx_queue_transfer(i);
	//memset(rx_transfer, 0, sizeof(rx_transfer));
	//usb_prepare_transfer(rx_transfer + 0, rx_buffer, RAWHID_RX_SIZE, 0);
	//usb_receive(RAWHID_RX_ENDPOINT, rx_transfer + 0);
	return RAWHID_RX_SIZE;
}

int usb_rawhid_send(const void *buffer, uint32_t timeout)
{
	transfer_t *xfer = tx_transfer + tx_head;
	uint32_t wait_begin_at = systick_millis_count;

	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		uint32_t status = usb_transfer_status(xfer);
		if (!(status & 0x80)) break; // transfer descriptor ready
		if (systick_millis_count - wait_begin_at > timeout) return 0;
		yield();
	}
	uint8_t *txdata = txbuffer + (tx_head * RAWHID_TX_SIZE);
	memcpy(txdata, buffer, RAWHID_TX_SIZE);
	arm_dcache_flush_delete(txdata, RAWHID_TX_SIZE );
	usb_prepare_transfer(xfer, txdata, RAWHID_TX_SIZE, 0);
	usb_transmit(RAWHID_TX_ENDPOINT, xfer);
	if (++tx_head >= TX_NUM) tx_head = 0;
	return RAWHID_TX_SIZE;
}

to :

Code:
int usb_rawhid_recv(void *buffer, uint32_t timeout)
{
	uint32_t wait_begin_at =micros();
	uint32_t tail = rx_tail;
	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		if (tail != rx_head) break;
		if (micros() - wait_begin_at > timeout)  {
			return 0;
		}
		yield();
	}
//	digitalWriteFast(0, LOW);
	if (++tail > RX_NUM) tail = 0;
	uint32_t i = rx_list[tail];
	rx_tail = tail;

	memcpy(buffer,  rx_buffer + i * RAWHID_RX_SIZE, RAWHID_RX_SIZE);
	rx_queue_transfer(i);
	//memset(rx_transfer, 0, sizeof(rx_transfer));
	//usb_prepare_transfer(rx_transfer + 0, rx_buffer, RAWHID_RX_SIZE, 0);
	//usb_receive(RAWHID_RX_ENDPOINT, rx_transfer + 0);
	return RAWHID_RX_SIZE;
}

int usb_rawhid_send(const void *buffer, uint32_t timeout)
{
	transfer_t *xfer = tx_transfer + tx_head;
	uint32_t wait_begin_at = micros();

	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		uint32_t status = usb_transfer_status(xfer);
		if (!(status & 0x80)) break; // transfer descriptor ready
		if (micros() - wait_begin_at > timeout) return 0;
		yield();
	}
	uint8_t *txdata = txbuffer + (tx_head * RAWHID_TX_SIZE);
	memcpy(txdata, buffer, RAWHID_TX_SIZE);
	arm_dcache_flush_delete(txdata, RAWHID_TX_SIZE );
	usb_prepare_transfer(xfer, txdata, RAWHID_TX_SIZE, 0);
	usb_transmit(RAWHID_TX_ENDPOINT, xfer);
	if (++tx_head >= TX_NUM) tx_head = 0;
	return RAWHID_TX_SIZE;
}

In summary, I changed all systick_millis_count to micros().

I don't know if those changes can bring other bugs, but so far, this has not caused me any problems. The communication is working and the data I receive is good.

Request: Implement those changes or something similar in the source code.
 
This is incompatible to everything.

Do you mean incompatible in the sense that older codes which specify the timeout time in milliseconds will need to be changed? If so, there is surely a simple way to make it work regardless. I think that using >= signs instead of > will make it possible to have a timeout of 0 ms.

If not, I'd like to know why it's incompatible with everything. I wouldn't want my control loop to fail for an unknown reasons.
 
Do you mean incompatible in the sense that older codes which specify the timeout time in milliseconds will need to be changed? If so, there is surely a simple way to make it work regardless. I think that using >= signs instead of > will make it possible to have a timeout of 0 ms.

If not, I'd like to know why it's incompatible with everything. I wouldn't want my control loop to fail for an unknown reasons.

I believe what Frank B is commenting on, is if you have a sketch that
calls: usb_rawhid_recv(myBuffer, 100);

They were expecting the code to wait for up to a 100ms or a tenth of a second to receive a packet, with this change the
code would only wait for .0001 seconds for a packet. Which could break any app that was expecting to wait the 100ms
 
I believe what Frank B is commenting on, is if you have a sketch that
calls: usb_rawhid_recv(myBuffer, 100);

They were expecting the code to wait for up to a 100ms or a tenth of a second to receive a packet, with this change the
code would only wait for .0001 seconds for a packet. Which could break any app that was expecting to wait the 100ms

Thanks KurtE for the comprehensive explanation.

I think the problem is that currently, if I call usb_rawhid_recv(myBuffer, 100); the code wait up to 101 ms, so 1 ms more than asked. This is due to the fact that systick_millis_count is an int and the comparison is done with a > instead of >=.

I tested the use of >= and it work as expected. Basically, I changed :

Code:
int usb_rawhid_recv(void *buffer, uint32_t timeout)
{
	uint32_t wait_begin_at = systick_millis_count;
	uint32_t tail = rx_tail;
	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		if (tail != rx_head) break;
		if (systick_millis_count - wait_begin_at > timeout)  {
			return 0;
		}
		yield();
	}
//	digitalWriteFast(0, LOW);
	if (++tail > RX_NUM) tail = 0;
	uint32_t i = rx_list[tail];
	rx_tail = tail;

	memcpy(buffer,  rx_buffer + i * RAWHID_RX_SIZE, RAWHID_RX_SIZE);
	rx_queue_transfer(i);
	//memset(rx_transfer, 0, sizeof(rx_transfer));
	//usb_prepare_transfer(rx_transfer + 0, rx_buffer, RAWHID_RX_SIZE, 0);
	//usb_receive(RAWHID_RX_ENDPOINT, rx_transfer + 0);
	return RAWHID_RX_SIZE;
}

int usb_rawhid_send(const void *buffer, uint32_t timeout)
{
	transfer_t *xfer = tx_transfer + tx_head;
	uint32_t wait_begin_at = systick_millis_count;

	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		uint32_t status = usb_transfer_status(xfer);
		if (!(status & 0x80)) break; // transfer descriptor ready
		if (systick_millis_count - wait_begin_at > timeout) return 0;
		yield();
	}
	uint8_t *txdata = txbuffer + (tx_head * RAWHID_TX_SIZE);
	memcpy(txdata, buffer, RAWHID_TX_SIZE);
	arm_dcache_flush_delete(txdata, RAWHID_TX_SIZE );
	usb_prepare_transfer(xfer, txdata, RAWHID_TX_SIZE, 0);
	usb_transmit(RAWHID_TX_ENDPOINT, xfer);
	if (++tx_head >= TX_NUM) tx_head = 0;
	return RAWHID_TX_SIZE;
}

to :

Code:
int usb_rawhid_recv(void *buffer, uint32_t timeout)
{
	uint32_t wait_begin_at = systick_millis_count;
	uint32_t tail = rx_tail;
	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		if (tail != rx_head) break;
		if (systick_millis_count - wait_begin_at >= timeout)  {
			return 0;
		}
		yield();
	}
//	digitalWriteFast(0, LOW);
	if (++tail > RX_NUM) tail = 0;
	uint32_t i = rx_list[tail];
	rx_tail = tail;

	memcpy(buffer,  rx_buffer + i * RAWHID_RX_SIZE, RAWHID_RX_SIZE);
	rx_queue_transfer(i);
	//memset(rx_transfer, 0, sizeof(rx_transfer));
	//usb_prepare_transfer(rx_transfer + 0, rx_buffer, RAWHID_RX_SIZE, 0);
	//usb_receive(RAWHID_RX_ENDPOINT, rx_transfer + 0);
	return RAWHID_RX_SIZE;
}

int usb_rawhid_send(const void *buffer, uint32_t timeout)
{
	transfer_t *xfer = tx_transfer + tx_head;
	uint32_t wait_begin_at = systick_millis_count;

	while (1) {
		if (!usb_configuration) return -1; // usb not enumerated by host
		uint32_t status = usb_transfer_status(xfer);
		if (!(status & 0x80)) break; // transfer descriptor ready
		if (systick_millis_count - wait_begin_at >= timeout) return 0;
		yield();
	}
	uint8_t *txdata = txbuffer + (tx_head * RAWHID_TX_SIZE);
	memcpy(txdata, buffer, RAWHID_TX_SIZE);
	arm_dcache_flush_delete(txdata, RAWHID_TX_SIZE );
	usb_prepare_transfer(xfer, txdata, RAWHID_TX_SIZE, 0);
	usb_transmit(RAWHID_TX_ENDPOINT, xfer);
	if (++tx_head >= TX_NUM) tx_head = 0;
	return RAWHID_TX_SIZE;
}

With this change, it is possible to have a timeout of 0 ms.

Edit :

When looking at the teensy3 core, I found another way to achieve a 0 ms timeout without changing > to >=. You just change :

Code:
if (systick_millis_count - wait_begin_at > timeout) return 0;

to :

Code:
if (systick_millis_count - wait_begin_at > timeout || !timeout) return 0;

I didn't test this but it seems like a good way to do it, although it won't fix the fact that the maximum timeout will always be 1 ms more than asked.
 
Last edited:
Hi KurtE,

I was wondering if anyone took a look into this issue. It is pretty annoying to have to manually change the code every time that I need to upload from a new computer.

I think this change is pretty simple and will not break any existing code.

Thanks a lot,

Bruno
 
Back
Top