0

I want to send Data through USB from STM32. I've written the following bare-metal code to do it. But my PC doesn't recognize USB device when plugged in.

#include "stm32f10x.h"

void USB_Endpoint_Configure(uint8_t endpoint, uint8_t type, uint8_t dir) {

  // Check the parameters.
  if (endpoint >= 16) {
    return;
  }

  // Set the endpoint type.
  *((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x00) = type;

  // Set the endpoint direction.
  *((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x01) = dir;

  // Set the endpoint max packet size.
  *((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x02) = 64;

  // Enable the endpoint.
  *((volatile unsigned int *)0x40005C00 + endpoint * 4 + 0x04) |= 0x00000001;
}

int USB_Endpoint_Write(uint8_t endpoint, const void *data, int size) {
  // Check if the endpoint is valid.
  if (endpoint >= 0x80 || endpoint >= 0xE0) {
    return -1;
  }

  // Check if the data is large enough to fill the endpoint buffer.
  if (size > 64) {
    return -1;
  }

  // Write the data to the endpoint buffer.
  for (int i = 0; i < size; i++) {
    *((uint8_t *)(0x40000000 + (endpoint << 7) + i)) = *((const uint8_t *)data + i);
  }

  // Send the data to the device.
  return 0;
}


int main(void){
    //USB_Init()
    // Reset the USB controller.
  RCC->APB1ENR |= RCC_APB1ENR_USBEN;
  *((volatile unsigned int *)0x40005C00) |= 0x00000001;
  while (*((volatile unsigned int *)0x40005C00) & 0x00000001);
    
    // Initialize the USB clock.
  RCC->APB1ENR |= 0x00000001;

  // Initialize the USB interrupts.
  NVIC_EnableIRQ(5);

    USB_Endpoint_Configure(0x00, 0x00, 0x00);
  USB_Endpoint_Configure(0x80, 0x00, 0x40);
    
    
    
    
    USB_Endpoint_Configure(0x00, 0x00, 0x00);
    
    
    char data[]="Hello WOrld!!!";
    
     USB_Endpoint_Write(0x00, data, sizeof(data));
     
     //Enable endpoint EP0
     *((uint8_t *)(0x40000000 + (0x00 << 7) + 0)) |= 1;
     
        // Wait for the data to be transferred.
        while (!*((uint8_t *)(0x40000000 + (0x00 << 7) + 1)) & 0x80) {
    // Do nothing.
        }
        
        //Disable endpoint EP0
     *((uint8_t *)(0x40000000 + (0x00 << 7) + 0)) |= 0;

}


I could do the same thing while coding through HAL libraries and it worked successfully. Hence, there are no physical issues with USB cable or device. So can anyone help to do it in Bare-Metal?? (I'm using STM32F103C8 chip)

  • 1
    For starters, compare the contents of USB configuration registers between working and non-working examples. You could notice some mismatch. – Ilya May 27 '23 at 15:34
  • not enough info here, doesnt look like nearly enough code to configure the chip for usb. when you use the hal you are bare metal already. I assume you mean without the hal instead of bare metal? – old_timer May 28 '23 at 16:49
  • there is no debugging shown, are you blinking leds and/or using uart output or a debugger to see that enumeration is happening and following every step of the process through enumeration into normal communication? a complete example and a information on debugging is likely required to help here. – old_timer May 28 '23 at 16:51
  • stm32 is far too generic as there are many incompatible stm32s, what specific chip. – old_timer May 28 '23 at 16:51

1 Answers1

2

USB happens to be more complicated than that. You should at least receive SETUP packet from host, answer it with something sensible, go on with bus enumeration before OS would consider your device working - and once you've presented itself, you can start exchanging your data.

Unfortunately it means some extra "chat" and that's where you learn about "USB descriptors" describing your device, its EPs, and so on. See in e.g. wireshark under linux how other devices do enumeration phase and do the same. FYI, in USB device cant send data on its own unless host asks it to do so and device answers with data. That's what EP buffers really for - hardware will sequence right answer from buffer when hosts asks. Reading USB specs is really wise idea, even if they're huge. F10x can do USB 1.x or Full-speed 2.0 - these aren't VERY big. But still big reading. You can try to find "usb in a nutshell" thing as well, its more pragmatic/simplified explanation.

You can also try to look on something like https://github.com/eddyem/stm32samples - this features some USB, bare metal style, even fakes some usb-serial bridge ICs to avoid coding OS drivers. Few other similar examples exist. This one uses (Cortex M3 side) interrupts to serve endpoints, I also met something doing EP serving in "poll mode" but doing SETUP phase and so on like that isn't exactly eye-candy code since you should basically include some USB negotiation logic.

Also: USB fairly strict on timings. Most notably, it got notion of "pulldown resistor" on D+ or D- line, and you better have this one software-controlled. Most USB-enabled boards would have USB pulldowns control circuit, its up to you to know where it happens to be on your board.

Flow is:

  1. Deactivate pulldown resistors early on boot.
  2. Initialize system doing whatever you wanted. You also apparently failing this part, see below on that.
  3. Once you think your USB ready for prime-time enable pulldown resistor.
  4. Host would spot device and try to enumerate it sending SETUP packet and so on.

Its advised to go like this because if your system init would fail strict usb timings (IIRC like 10 ms since pulldown resistor detected on bus), host could/would consider your device faulty and could ignore it or do all kinds of weird "recovery" stuff (e.g. bus resets/stall/.... you apparently not handle yet). So its much safer to complete all ops - and enable pulldown once you can REALLY afford strict USB timeout timings.

On side note Linux makes things considerably easier since you'll at least see whether OS detected your device and what it got in "dmesg" output, giving clear hints whether pullups worked, how OS seen your USB descriptors, whether enumeration worked, and tools like wireshark can help to see USB I/O packets flow in no time (its literally "modprobe usbmon" and then run just wireshark, select USB as source - just 2 steps to get low level USB debug going).

Also it should be noted USB needs exact timings and relies on accurate clocking. Clock deviation limited to 500ppm, HSI RC used on boot is way off the track. You can't just give USB block arbitrary clock and get away with it.

So, to use USB you need to switch on HSE (XTAL), configure PLL to use HSE and output either 48MHz (using /1 USB divider, see clock-tree in F10x TRM manual) or 72MHz (using /1.5 divider on PLL output). This also means you'll need flash wait states configured, since flash can't run faster than 24MHz without wait states. On side note it means not all XTAL values are suitable for USB: you should be able to output 48MHz to USB block, within 500 ppm margin. PLL somewhat relaxes things, but, still, USB is picky. Your code apparently lacks this part of configuration if I got it right. That's where advice about pulldown comes handy since HSE startup, PLL lock and so on aren't "instant" (e.g. xtal on HSE can easily take few ms to start), add something else, or spit some words to (slower but much easier to bring up) USART - and you can easily miss 10ms timeout deadline. So idea is to appear on USB bus when you really feel ready for prime time, not a millisecond before.

XCRH
  • 21
  • 3