cancel
Showing results for 
Search instead for 
Did you mean: 

When is HAL USB ready for me to send?

temp2010
Senior

I have taken the STM32Cube_FW_F1_V1.8.0 example CDC_Standalone and adapted it to my purposes. The original example intends to forward bi-direction USB communication over a serial port. I have removed the serial port stuff and intend to simply send messages over USB from my STM32F105* mcu to my USB host.

While I have gotten this somewhat working, I find that I'm missing some detail to get it working correctly. It seems that I can't get the ball rolling. For example, if I just try to send the string "Hello World!\n" to the USB from the STM32, that string does NOT come out. But if some other activity occurs first, then it does.

Subsequent to the above trouble, I added CAN bus stuff. My intent is to simply log all CAN bus traffic onto USB to my USB host. Well, if I power off my CAN bus data generator and restart my STM32, then nothing comes to my USB host. However, when I turn my CAN bus data generator on, all of a sudden my log messages start appearing on the USB host. Furthermore, they are preceded by my "Hello World!\n" message.

Therefore, it seems like the sending out of the USB message was somehow held up until some other activity occurred. I don't know if the CAN and USB interrupts are independent or conflicting. Also, there is a HAL_TIM_PeriodElapsedCallback() function I inherited from the example, and I don't know if maybe the problem is that this callback isn't called until some other activity occurred.

I'll go try and read through the code to figure out when HAL_TIM_PeriodElapsedCallback() should be called, but otherwise I would greatly appreciate some hints or direct comments as to why the ball doesn't start rolling on its own. Why don't I get "Hello World!\n" out immediately?

(Note, I'm using a virtual com port on the USB host, of course, and running TeraTerm on that USB host.)

UPDATE: HAL_TIM_PeriodElapsedCallback() is indeed being called immediately. It in turn calls USBD_CDC_SetTxBuffer() and USBD_CDC_TransmitPacket() immediately. But nothing shows up on the USB host. I can see two line feeds were supposed to have been sent, totaling "Hello World!\nStart\n", and nothing shows on the USB host. I don't think it's a USB host side flushing issue. It could be an STM32 side flushing issue, but more likely some other missing event on the STM32 side...

UPDATE: My program will start and attempt to send "Hello World!\nStart\n". I see nothing on the USB host. But if I send a single char, such as "a" **from** the USB host to the STM32, **then** I get the "Hello World!\nStart\n" back out, as well as a looped back value of the "a". That is, the USB receive code in the STM32 loops back (echoes) the "a" back out. That loop back uses the same transmit() function I've written as the hello world. The difference, however, is that the loop back code calls USBD_CDC_ReceivePacket(). I would think I could transmit to usb without having to call USBD_CDC_ReceivePacket()... Simply calling USB_Receive_Complete_Callback() from the end of my transmit function does NOT get the ball rolling. I must actually send something from the USB host so that the STM32 actually RECEIVES something.

UPDATE: It only takes a single char from the USB host. I added to main a delay loop to send out "Tick\n" every 3 seconds. Once I send a single char from the USB host, my "Hello World!\nStart\n" comes out as well as "Tick\n" every 3 seconds. I do **not** have to send another char from the USB host for each subsequent "Tick\n" message to come out. Therefore, it seems like the very first receive is somehow greasing the wheels for as many subsequent transmits as I want. I **really** want to figure out what this is, so that my "Hello World!\nStart\n" will come out on its own, with no need for the USB host to send the first char.

8 REPLIES 8
Pavel A.
Evangelist III

>  I don't know if the CAN and USB interrupts are independent or conflicting.

They are independent but may be conflicting.

The USB "sender" requires timely handling of USB device interrupts. Otherwise TX won't work, as you see;.

So it is necessary to properly configure the interrupts - relative priorities and preemption.

The USB interrupt handler is quite complex and long, so try to give it priority.

-- pa

temp2010
Senior

Thanks, PA, but I don't think that's my issue at the moment.

If I put a single "HAL_Delay(1000);" between my "USBD_Start(&USBD_Device);" and my transmit hello world, it works as expected. So it seems that something is happening on a different thread during the "HAL_Delay(1000);". I would prefer to know what this is and poll status for when it is complete. I might poll in a tight loop without delay, or I might add a tiny delay. (I don't know how thread yielding is handled.)

Delay is not a good way to detect USB TX readiness.

In case of CDC, you'll find in examples something like this (without a RTOS):

/* in usb_cdc_if.c  */
 
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
  if (hcdc->TxState != 0)
      return USBD_BUSY;
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
  return USBD_CDC_TransmitPacket(&hUsbDeviceFS);
}

In line 6, hcdc->TxState is managed by the interrupt handler. It should clear when the previous data goes out on the wire.

If the related interrupt is delayed by something else or gets lost (the USB code is very flaky) the TxState can stuck in busy state.

To improve on this, you can detect timeout and do something meaningful.

-- pa

S.Ma
Principal

I think the delay will just make some USB interrupts kick in and maybe enumerate the device prior to send the user hello world.

temp2010
Senior

Pavel & .

LOL, of course I know delay is not a good way to detect USB TX readiness. Thus this line of questions!

Well, I had already seen the check "hcdc->TxState != 0", but it appears that this is NOT enough. I've written a "USB_Transmit_Ready()" function that I call in a loop, only existing the loop when the return value is non-zero. I've stepped through the code while operating, and I find that all these things I'm checking are STILL not enough. Specifically, if I exit the loop immediately upon USB_Transmit_Ready(), my following USB_Transmit() doesn't work. But if I add a 10ms delay, it does work. So, all those things being checked are not sufficient. There must be yet something ELSE that is what needs to be checked.

Any ideas of what that other thing is? What might be that last and final thing that gets set or occurs at which time immediately thereafter my USB_Transmit() should work?

Below is my USB_Transmit_Ready() code. All of these things checked below are indeed different before and after the 10ms delay. However they aren't sufficient. There's probably one single other thing I should be checking instead. IS THERE ANY REASONABLE DOC ON THIS STUFF ANYWHERE?!?!?!? I can't believe there's not a published "ready" function already... Realize I'm talking about immediately after USBD_Start(), and not simply *between* transmissions.

int USB_Transmit_Ready(USBD_HandleTypeDef *pdev)	// HgF - Return 1 if USB ready for transmissions
{
	USBD_CDC_HandleTypeDef   *hcdc;
	uint32_t TxStateVar;
	int iRC = 0;
	if (pdev) {
		if (pdev->dev_state == USBD_STATE_CONFIGURED) {
			if (pdev->ep0_state == USBD_EP0_IDLE) {
				hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
				if (hcdc != NULL) {
					TxStateVar = hcdc->TxState;
					if (TxStateVar==0) iRC = 1;
				}
			}
		}
	}
	return(iRC);
}

My bad. Of course this simple check for TxState is not enough.

So what happens after USBD_Start(): First, the host must enumerate the device (configure the device address, read all descriptors etc).

Before this the device obviously cannot send anything. Even then device cannot send yet, until some host application (terminal) starts reading.

That's, the device starts getting IN tokens from the host.

The driver or controller does not seem to expose this. At least I haven't found.

Only after one piece of data went thru, we can assume that somebody is reading and feed next piece.

And there can be bad USB cables or hubs, re-transmissions, errors...

-- pa

temp2010
Senior

Thanks again, sincerely, Pavel. If I read you correctly, you're agreeing with me and extending my result to say it can't be done. That is, the controller does NOT expose the fact when it's genuinely ready for a first transmit. This in turns means I must either use the delay, or "not speak until spoken to". (That is, require the host to send to the STM32 first, before the STM32 ever sends to the host. I like that less than the 10ms delay. So the delay it shall remain!) I could just throw one byte at a time over the wall and search for some kind of success status, but again that's nasty. 10ms delay it is.

Speaking of all this, I simply can not find any actual doc on this USB. That's CRAZY. I'm posting separately in a moment about CAN doc...

Ben K
Senior III

I have this functionality implemented here: https://github.com/IntergatedCircuits/CanDybugFW

This project is using a custom USB device driver, which gives callbacks when the line parameters are changed by the host (e.g. the baudrate). In my experience this is the best way to detect when a virtual COM port is opened. You can use the same logic in the ST code, check for a special baudrate value in the CDC Control call, and start communication after you receive it.