I've been working on a USB host stack and driver for ChibiOS for the last couple of weeks. The code quality is ultra-beta for now (especially the low level driver), but it works with the (few) devices I've tested.
The driver is split in two (as the rest of the ChibiOS HAL):
1) a high level driver which handles:
* port connection/disconnection detection
* enumeration
* class driver loading by Class/Subclass/Protocol and VID/PID (TODO)
2) a driver for the STM32 OTG IP:
* full-speed only
* Slave-mode only (no DMA)
* MUCH room for improvement/optimization yet, the driver has to be tested thoroughly.
* Support for all the transfer types, but only tested Control SETUP/IN/OUT, Bulk IN/OUT, Interrupt IN.
* I will test Isochronous IN with the USB Video Class driver; I don't have other devices to test INT/ISO OUT...
On top of the high level driver, we can write class drivers. For now, there are only:
- Mass Storage driver, which exposes a ChibiOS Block Device (to connect, for instance, to FATFS).
- HUB driver, tightly integrated with the high level driver, to connect multiple devices to the USB port.
- Interface Association driver (dummy driver which loads drivers for interface groups - necessary for USB Video Class and USB Audio Class)
- UVC class skeleton (no code yet).
The high level driver API is inspired in various hosts/libraries including Linux and libUSB. I think the driver is basic but quite complete (for example, it supports multiple devices at the same time, and a clean asyncrhonous API). The high level driver exposes two kinds of APIs to perform transfers:
- Synchronous API, for Control and Bulk only, which send a message while blocking the thread for the answer:
See example in usbh_msd.c. The synchronous API is built upon the asyncrhonous API.
Code: Select all
...
stat = usbhBulkTransfer(ep, data, len, &actual_len, timeout);
//Here, the transfer is done (or an error ocurred)
...
- Asynchronous API, for any transfer type, which can be used in interrupts:
See example in usbh_hub.c
Code: Select all
func() {
...
osalSysLock();
usbhURBSubmitI(&urb);
osalSysUnlock();
...
}
static void _urb_complete_callback(usbh_urb_t *urb) {
switch (urb->status) {
case USBH_URBSTATUS_TIMEOUT:
...
break;
case USBH_URBSTATUS_OK:
...
break;
case USBH_URBSTATUS_CANCELLED:
...
break;
default:
...
break;
}
usbhURBObjectResetI(urb);
usbhURBSubmitI(urb);
}
The low level driver is inspired primarily in the Linux source code for the DesignWare OTG driver (much cleaner than the ST implementation, which is quite buggy). The code should be adaptable to any OCHI/ECHI implementation (for instance, I think the NXP LPCxxxx are OHCI/ECHI), because the basic idea is similar: when you submit an URB, the driver queues it in the endpoint, and queues the endpoint for processing in the host scheduler.
Thus, it is possible to push ahead of time some URBs, to maximize USB bandwidth. For example, a possible UVC (USB Video Class) camera driver could do something like this:
Code: Select all
init() {
initialize a mailbox
reserve a memory pool of 10 buffers
initialize two URBs
mempool alloc & submit URB to ISO IN endpoint
mempool alloc & submit URB to ISO IN endpoint
}
callback() {
push full buffer to mailbox
reset URB
mempool alloc & submit URB to ISO IN endpoint
}
reading thread() {
while(1) {
wait for mailbox
do something with the full buffer
mempool free the buffer
}
}
For the high level driver to work, the function usbhMainLoop has to be called periodically. This function polls the connected hubs (including the root hub) for port status changes (connect/disconnect, etc), and enumerates devices and loads appropriate drivers. The poll rate and priority are not critical (the example provided uses 100ms).
The provided example enumerates the connected devices and prints device/configuration details. Also, if the device is a mass storage device, then loads the Mass Storage Driver. The MSD then loads one block driver for each Logical Unit (LUN). The TestThread detects the BLK_READY state, and begins a quick test (read/write/file listing), using the FATFS library. This works also through USB hubs.
All the devices I have lying around get enumerated ok:
- 3 pendrives
- 1 USB hard drive
- 2 mice
- 1 keyboard
- 4 webcams
- 1 MIDI interface
- 2 hubs
- 3 FTDI USB/serial converters
- 1 Jlink debug pod
Some details:
1) Sources:
src/usbh.c: The high level driver
src/usbh_lld.c: The low level driver for STM32 OTG
src/usbh_debug.c: Debug functions
src/usbh_desciter.c: Configuration descriptor iterator routines (see example in usbhDevicePrintConfiguration)
src/usbh_hub.c: HUB class driver
src/usbh_msd.c: Mass Storage class driver
src/usbh_uvc.c: UVC driver (skeleton)
2) Headers:
include/usbh_conf.h: Configurations (Tune this file)
include/usbh.h: Main driver header (API)
include/usbh_msd.h: Header for usbh_msd.c
include/usbh_hub.h: Header for usbh_hub.c
include/usbh_desciter.h: Header for usbh_desciter.c
To test:
1. I use the SVN version of ChibiOS
2. The stm32_otg.h file is a modified version of the one distributed with ChibiOS (one bug correction, and some additions). You should replace the ChibiOS (in hal/ports/STM32/LLD/OTGv1/) version with the one provided.
3. Configure your MCU in mcuconf.h, board.h, Makefile. Currently, I use a custom board with the STM32F207.
4. Configure the IOs in main(), especially the SD1 UART pins, the VBUS enable pin (if any), and the USB pins (note that the driver doesn't work with external PHYs yet, only with the internal Low/Full Speed PHY).
5. Configure the usbh driver in usbh_conf.h, especially STM32_USBH_USE_OTG: 1 = OTG_FS, 2 = OTG_HS (in Full speed mode)
6. Connect a PC's serial port using 230400bps 8N1; the example uses serial driver SD1 (UART1_TX/RX).
7. Plug a pendrive (best if there's nothing critical in there!).
Future improvements might include (also, see TODOs in all files):
- High level: Event sources for connection, disconnection, etc.
- High level: More class drivers (HID, UVC)
- High level: Suspend/Resume support
- LLD: Cleaner code
- LLD: DMA and High speed support (don't have a board with HS phy)
- LLD: Suspend/Resume support
- General: more options to compile-out portions of code to reduce flash/RAM usage.
One important detail: The driver makes use of the Linux Kernel list.h header (renamed as usbh_list.h). See http://github.com/torvalds/linux/blob/m ... nux/list.h. This file will have to be rewritten. It shouldn't be a big job, because not many functions are used.
Any comments are welcome!
Diego.