Add UVC driver and test/example
This commit is contained in:
parent
a6898a525f
commit
50dda7cff2
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio
|
||||
Copyright (C) 2015..2016 Diego Ismirlian, TISA, (dismirlian (at) google's mail)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef USBH_INCLUDE_USBH_UVC_H_
|
||||
#define USBH_INCLUDE_USBH_UVC_H_
|
||||
|
||||
#include "hal_usbh.h"
|
||||
|
||||
#if HAL_USE_USBH && HAL_USBH_USE_UVC
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver pre-compile time settings. */
|
||||
/*===========================================================================*/
|
||||
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Derived constants and error checks. */
|
||||
/*===========================================================================*/
|
||||
#define USBHUVC_MAX_STATUS_PACKET_SZ 16
|
||||
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver data structures and types. */
|
||||
/*===========================================================================*/
|
||||
|
||||
|
||||
typedef enum {
|
||||
UVC_CS_INTERFACE = 0x24,
|
||||
UVC_CS_ENDPOINT = 0x25
|
||||
} usbh_uvc_cstype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CC_VIDEO = 0x0e
|
||||
} usbh_uvc_cctype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_SC_UNKNOWN = 0x00,
|
||||
UVC_SC_VIDEOCONTROL,
|
||||
UVC_SC_VIDEOSTREAMING,
|
||||
UVC_SC_VIDEO_INTERFACE_COLLECTION
|
||||
} usbh_uvc_sctype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_VC_UNDEF = 0x00,
|
||||
UVC_VC_HEADER,
|
||||
UVC_VC_INPUT_TERMINAL,
|
||||
UVC_VC_OUTPUT_TERMINAL,
|
||||
UVC_VC_SELECTOR_UNIT,
|
||||
UVC_VC_PROCESSING_UNIT,
|
||||
UVC_VC_EXTENSION_UNIT
|
||||
} usbh_uvc_vctype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_VS_UNDEF = 0x00,
|
||||
UVC_VS_INPUT_HEADER,
|
||||
UVC_VS_OUTPUT_HEADER,
|
||||
UVC_VS_STILL_IMAGE_FRAME,
|
||||
UVC_VS_FORMAT_UNCOMPRESSED,
|
||||
UVC_VS_FRAME_UNCOMPRESSED,
|
||||
UVC_VS_FORMAT_MJPEG,
|
||||
UVC_VS_FRAME_MJPEG,
|
||||
UVC_VS_RESERVED_0,
|
||||
UVC_VS_RESERVED_1,
|
||||
UVC_VS_FORMAT_MPEG2TS,
|
||||
UVC_VS_RESERVED_2,
|
||||
UVC_VS_FORMAT_DV,
|
||||
UVC_VS_COLOR_FORMAT,
|
||||
UVC_VS_RESERVED_3,
|
||||
UVC_VS_RESERVED_4,
|
||||
UVC_VS_FORMAT_FRAME_BASED,
|
||||
UVC_VS_FRAME_FRAME_BASED,
|
||||
UVC_VS_FORMAT_STREAM_BASED
|
||||
} usbh_uvc_vstype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_TT_VENDOR_SPECIFIC = 0x0100,
|
||||
UVC_TT_STREAMING = 0x0101,
|
||||
UVC_ITT_VENDOR_SPECIFIC = 0x0200,
|
||||
UVC_ITT_CAMERA = 0x0201,
|
||||
UVC_ITT_MEDIA_TRANSPORT_INPUT = 0x0202,
|
||||
UVC_OTT_VENDOR_SPECIFIC = 0x0300,
|
||||
UVC_OTT_DISPLAY = 0x0301,
|
||||
UVC_OTT_MEDIA_TRANSPORT = 0x0302
|
||||
} usbh_uvc_tttype_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_SET_CUR = 0x01,
|
||||
UVC_GET_CUR = 0x81,
|
||||
UVC_GET_MIN = 0x82,
|
||||
UVC_GET_MAX = 0x83,
|
||||
UVC_GET_RES = 0x84,
|
||||
UVC_GET_LEN = 0x85,
|
||||
UVC_GET_INFO = 0x86,
|
||||
UVC_GET_DEF = 0x87
|
||||
} usbh_uvc_ctrlops_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CTRL_VC_CONTROL_UNDEFINED = 0x00,
|
||||
UVC_CTRL_VC_VIDEO_POWER_MODE_CONTROL = 0x01,
|
||||
UVC_CTRL_VC_REQUEST_ERROR_CODE_CONTROL = 0x02,
|
||||
} usbh_uvc_ctrl_vc_interface_controls_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CTRL_SU_CONTROL_UNDEFINED = 0x00,
|
||||
UVC_CTRL_SU_INPUT_SELECT_CONTROL = 0x01,
|
||||
} usbh_uvc_ctrl_vc_selectorunit_controls_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CTRL_CT_CONTROL_UNDEFINED = 0x00,
|
||||
UVC_CTRL_CT_SCANNING_MODE_CONTROL = 0x01,
|
||||
UVC_CTRL_CT_AE_MODE_CONTROL = 0x02,
|
||||
UVC_CTRL_CT_AE_PRIORITY_CONTROL = 0x03,
|
||||
UVC_CTRL_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL = 0x04,
|
||||
UVC_CTRL_CT_EXPOSURE_TIME_RELATIVE_CONTROL = 0x05,
|
||||
UVC_CTRL_CT_FOCUS_ABSOLUTE_CONTROL = 0x06,
|
||||
UVC_CTRL_CT_FOCUS_RELATIVE_CONTROL = 0x07,
|
||||
UVC_CTRL_CT_FOCUS_AUTO_CONTROL = 0x08,
|
||||
UVC_CTRL_CT_IRIS_ABSOLUTE_CONTROL = 0x09,
|
||||
UVC_CTRL_CT_IRIS_RELATIVE_CONTROL = 0x0A,
|
||||
UVC_CTRL_CT_ZOOM_ABSOLUTE_CONTROL = 0x0B,
|
||||
UVC_CTRL_CT_ZOOM_RELATIVE_CONTROL = 0x0C,
|
||||
UVC_CTRL_CT_PANTILT_ABSOLUTE_CONTROL = 0x0D,
|
||||
UVC_CTRL_CT_PANTILT_RELATIVE_CONTROL = 0x0E,
|
||||
UVC_CTRL_CT_ROLL_ABSOLUTE_CONTROL = 0x0F,
|
||||
UVC_CTRL_CT_ROLL_RELATIVE_CONTROL = 0x10,
|
||||
UVC_CTRL_CT_PRIVACY_CONTROL = 0x11
|
||||
} usbh_uvc_ctrl_vc_cameraterminal_controls_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CTRL_PU_CONTROL_UNDEFINED = 0x00,
|
||||
UVC_CTRL_PU_BACKLIGHT_COMPENSATION_CONTROL = 0x01,
|
||||
UVC_CTRL_PU_BRIGHTNESS_CONTROL = 0x02,
|
||||
UVC_CTRL_PU_CONTRAST_CONTROL = 0x03,
|
||||
UVC_CTRL_PU_GAIN_CONTROL = 0x04,
|
||||
UVC_CTRL_PU_POWER_LINE_FREQUENCY_CONTROL = 0x05,
|
||||
UVC_CTRL_PU_HUE_CONTROL = 0x06,
|
||||
UVC_CTRL_PU_SATURATION_CONTROL = 0x07,
|
||||
UVC_CTRL_PU_SHARPNESS_CONTROL = 0x08,
|
||||
UVC_CTRL_PU_GAMMA_CONTROL = 0x09,
|
||||
UVC_CTRL_PU_WHITE_BALANCE_TEMPERATURE_CONTROL = 0x0A,
|
||||
UVC_CTRL_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL = 0x0B,
|
||||
UVC_CTRL_PU_WHITE_BALANCE_COMPONENT_CONTROL = 0x0C,
|
||||
UVC_CTRL_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL = 0x0D,
|
||||
UVC_CTRL_PU_DIGITAL_MULTIPLIER_CONTROL = 0x0E,
|
||||
UVC_CTRL_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL = 0x0F,
|
||||
UVC_CTRL_PU_HUE_AUTO_CONTROL = 0x10,
|
||||
UVC_CTRL_PU_ANALOG_VIDEO_STANDARD_CONTROL = 0x11,
|
||||
UVC_CTRL_PU_ANALOG_LOCK_STATUS_CONTROL = 0x12,
|
||||
} usbh_uvc_ctrl_vc_processingunit_controls_t;
|
||||
|
||||
typedef enum {
|
||||
UVC_CTRL_VS_CONTROL_UNDEFINED = 0x00,
|
||||
UVC_CTRL_VS_PROBE_CONTROL = 0x01,
|
||||
UVC_CTRL_VS_COMMIT_CONTROL = 0x02,
|
||||
UVC_CTRL_VS_STILL_PROBE_CONTROL = 0x03,
|
||||
UVC_CTRL_VS_STILL_COMMIT_CONTROL = 0x04,
|
||||
UVC_CTRL_VS_STILL_IMAGE_TRIGGER_CONTROL = 0x05,
|
||||
UVC_CTRL_VS_STREAM_ERROR_CODE_CONTROL = 0x06,
|
||||
UVC_CTRL_VS_GENERATE_KEY_FRAME_CONTROL = 0x07,
|
||||
UVC_CTRL_VS_UPDATE_FRAME_SEGMENT_CONTROL = 0x08,
|
||||
UVC_CTRL_VS_SYNCH_DELAY_CONTROL = 0x09
|
||||
} usbh_uvc_ctrl_vs_interface_controls_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubType;
|
||||
uint8_t bFormatIndex;
|
||||
uint8_t bNumFrameDescriptors;
|
||||
uint8_t bmFlags;
|
||||
uint8_t bDefaultFrameIndex;
|
||||
uint8_t bAspectRatioX;
|
||||
uint8_t bAspectRatioY;
|
||||
uint8_t bmInterfaceFlags;
|
||||
uint8_t bCopyProtect;
|
||||
} __attribute__((__packed__)) usbh_uvc_format_mjpeg_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubType;
|
||||
uint8_t bFrameIndex;
|
||||
uint8_t bmCapabilities;
|
||||
uint16_t wWidth;
|
||||
uint16_t wHeight;
|
||||
uint32_t dwMinBitRate;
|
||||
uint32_t dwMaxBitRate;
|
||||
uint32_t dwMaxVideoFrameBufferSize;
|
||||
uint32_t dwDefaultFrameInterval;
|
||||
uint8_t bFrameIntervalType;
|
||||
uint32_t dwFrameInterval[0];
|
||||
} __attribute__((__packed__)) usbh_uvc_frame_mjpeg_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubType;
|
||||
uint8_t bFrameIndex;
|
||||
uint8_t bmCapabilities;
|
||||
uint16_t wWidth;
|
||||
uint16_t wHeight;
|
||||
uint32_t dwMinBitRate;
|
||||
uint32_t dwMaxBitRate;
|
||||
uint32_t dwMaxVideoFrameBufferSize;
|
||||
uint32_t dwDefaultFrameInterval;
|
||||
uint8_t bFrameIntervalType;
|
||||
uint32_t dwFrameInterval[0];
|
||||
} __attribute__((__packed__)) usbh_uvc_frame_uncompressed_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubType;
|
||||
uint8_t bFormatIndex;
|
||||
uint8_t bNumFrameDescriptors;
|
||||
uint8_t guidFormat[16];
|
||||
uint8_t bBitsPerPixel;
|
||||
uint8_t bDefaultFrameIndex;
|
||||
uint8_t bAspectRatioX;
|
||||
uint8_t bAspectRatioY;
|
||||
uint8_t bmInterfaceFlags;
|
||||
uint8_t bCopyProtect;
|
||||
} __attribute__((__packed__)) usbh_uvc_format_uncompressed;
|
||||
|
||||
typedef struct {
|
||||
uint16_t bmHint;
|
||||
uint8_t bFormatIndex;
|
||||
uint8_t bFrameIndex;
|
||||
uint32_t dwFrameInterval;
|
||||
uint16_t wKeyFrameRate;
|
||||
uint16_t wPFrameRate;
|
||||
uint16_t wCompQuality;
|
||||
uint16_t wCompWindowSize;
|
||||
uint16_t wDelay;
|
||||
uint32_t dwMaxVideoFrameSize;
|
||||
uint32_t dwMaxPayloadTransferSize;
|
||||
// uint32_t dwClockFrequency;
|
||||
// uint8_t bmFramingInfo;
|
||||
// uint8_t bPreferedVersion;
|
||||
// uint8_t bMinVersion;
|
||||
// uint8_t bMaxVersion;
|
||||
} __attribute__((__packed__)) usbh_uvc_ctrl_vs_probecommit_data_t;
|
||||
|
||||
|
||||
|
||||
/* D0: Frame ID.
|
||||
* For frame-based formats, this bit toggles between 0 and 1 every time a new video frame begins.
|
||||
* For stream-based formats, this bit toggles between 0 and 1 at the start of each new codec-specific
|
||||
* segment. This behavior is required for frame-based payload formats (e.g., DV) and is optional
|
||||
* for stream-based payload formats (e.g., MPEG-2 TS). For stream-based formats, support for this
|
||||
* bit must be indicated via the bmFramingInfofield of the Video Probe and Commitcontrols
|
||||
* (see section 4.3.1.1, “Video Probe and Commit Controls”).
|
||||
*
|
||||
* D1: End of Frame.
|
||||
* This bit is set if the following payload data marks the end of the current video or still image
|
||||
* frame (for framebased formats), or to indicate the end of a codec-specific segment
|
||||
* (for stream-based formats). This behavior is optional for all payload formats.
|
||||
* For stream-based formats, support for this bit must be indicated via the bmFramingInfofield
|
||||
* of the Video Probe and CommitControls (see section 4.3.1.1, “Video Probe and Commit Controls”).
|
||||
*
|
||||
* D2: Presentation Time.
|
||||
* This bit is set if the dwPresentationTimefield is being sent as part of the header.
|
||||
*
|
||||
* D3: Source Clock Reference
|
||||
* This bit is set if the dwSourceClockfield is being sent as part of the header.
|
||||
*
|
||||
* D4: Reserved
|
||||
*
|
||||
* D5: Still Image
|
||||
* This bit is set ifthe following data is part of a still image frame, and is only used for
|
||||
* methods 2 and 3 of still image capture.
|
||||
*
|
||||
* D6: Error
|
||||
* This bit is set ifthere was an error in the video or still image transmission
|
||||
* for this payload. The StreamError Code control would reflect the cause of the error.
|
||||
*
|
||||
* D7: End of header
|
||||
* This bit is set if this is the last header group in the packet, where the
|
||||
* header group refers to this field and any optional fields identified by the bits in this
|
||||
* field (Defined for future extension)
|
||||
*/
|
||||
|
||||
#define UVC_HDR_EOH (1 << 7) /* End of header */
|
||||
#define UVC_HDR_ERR (1 << 6) /* Error */
|
||||
#define UVC_HDR_STILL (1 << 5) /* Still Image */
|
||||
#define UVC_HDR_SCR (1 << 3) /* Source Clock Reference */
|
||||
#define UVC_HDR_PT (1 << 2) /* Presentation Time */
|
||||
#define UVC_HDR_EOF (1 << 1) /* End of Frame */
|
||||
#define UVC_HDR_FID (1 << 0) /* Frame ID */
|
||||
|
||||
|
||||
|
||||
typedef struct USBHUVCDriver USBHUVCDriver;
|
||||
|
||||
#define USBHUVC_MESSAGETYPE_STATUS 1
|
||||
#define USBHUVC_MESSAGETYPE_DATA 2
|
||||
|
||||
|
||||
#define _usbhuvc_message_base_data \
|
||||
uint16_t type; \
|
||||
uint16_t length; \
|
||||
systime_t timestamp;
|
||||
|
||||
typedef struct {
|
||||
_usbhuvc_message_base_data
|
||||
} usbhuvc_message_base_t;
|
||||
|
||||
typedef struct {
|
||||
_usbhuvc_message_base_data
|
||||
USBH_DECLARE_STRUCT_MEMBER(uint8_t data[0]);
|
||||
} usbhuvc_message_data_t;
|
||||
|
||||
typedef struct {
|
||||
_usbhuvc_message_base_data
|
||||
USBH_DECLARE_STRUCT_MEMBER(uint8_t data[USBHUVC_MAX_STATUS_PACKET_SZ]);
|
||||
} usbhuvc_message_status_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
USBHUVC_STATE_UNINITIALIZED = 0, //must call usbhuvcObjectInit
|
||||
USBHUVC_STATE_STOP = 1, //the device is disconnected
|
||||
USBHUVC_STATE_ACTIVE = 2, //the device is connected
|
||||
USBHUVC_STATE_READY = 3, //the device has negotiated the parameters
|
||||
USBHUVC_STATE_STREAMING = 4, //the device is streaming data
|
||||
USBHUVC_STATE_BUSY = 5 //the driver is busy performing some action
|
||||
} usbhuvc_state_t;
|
||||
|
||||
|
||||
struct USBHUVCDriver {
|
||||
/* inherited from abstract class driver */
|
||||
_usbh_base_classdriver_data
|
||||
|
||||
usbhuvc_state_t state;
|
||||
|
||||
usbh_ep_t ep_int;
|
||||
usbh_ep_t ep_iso;
|
||||
|
||||
usbh_urb_t urb_iso;
|
||||
usbh_urb_t urb_int;
|
||||
|
||||
if_iterator_t ivc;
|
||||
if_iterator_t ivs;
|
||||
|
||||
USBH_DECLARE_STRUCT_MEMBER(usbh_uvc_ctrl_vs_probecommit_data_t pc);
|
||||
USBH_DECLARE_STRUCT_MEMBER(usbh_uvc_ctrl_vs_probecommit_data_t pc_min);
|
||||
USBH_DECLARE_STRUCT_MEMBER(usbh_uvc_ctrl_vs_probecommit_data_t pc_max);
|
||||
|
||||
mailbox_t mb;
|
||||
msg_t mb_buff[HAL_USBHUVC_MAX_MAILBOX_SZ];
|
||||
|
||||
memory_pool_t mp_data;
|
||||
void *mp_data_buffer;
|
||||
|
||||
memory_pool_t mp_status;
|
||||
usbhuvc_message_status_t mp_status_buffer[HAL_USBHUVC_STATUS_PACKETS_COUNT];
|
||||
|
||||
mutex_t mtx;
|
||||
};
|
||||
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver macros. */
|
||||
/*===========================================================================*/
|
||||
|
||||
|
||||
/*===========================================================================*/
|
||||
/* External declarations. */
|
||||
/*===========================================================================*/
|
||||
|
||||
extern USBHUVCDriver USBHUVCD[HAL_USBHUVC_MAX_INSTANCES];
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void usbhuvcObjectInit(USBHUVCDriver *uvcd);
|
||||
|
||||
static inline usbhuvc_state_t usbhuvcGetState(USBHUVCDriver *uvcd) {
|
||||
return uvcd->state;
|
||||
}
|
||||
|
||||
bool usbhuvcVCRequest(USBHUVCDriver *uvcdp,
|
||||
uint8_t bRequest, uint8_t entity, uint8_t control,
|
||||
uint16_t wLength, uint8_t *data);
|
||||
bool usbhuvcVSRequest(USBHUVCDriver *uvcdp,
|
||||
uint8_t bRequest, uint8_t control,
|
||||
uint16_t wLength, uint8_t *data);
|
||||
bool usbhuvcFindVSDescriptor(USBHUVCDriver *uvcdp,
|
||||
generic_iterator_t *ics,
|
||||
uint8_t bDescriptorSubtype,
|
||||
bool start);
|
||||
uint32_t usbhuvcEstimateRequiredEPSize(USBHUVCDriver *uvcdp, const uint8_t *formatdesc,
|
||||
const uint8_t *framedesc, uint32_t dwFrameInterval);
|
||||
|
||||
#if USBH_DEBUG_ENABLE && USBHUVC_DEBUG_ENABLE_INFO
|
||||
void usbhuvcPrintProbeCommit(const usbh_uvc_ctrl_vs_probecommit_data_t *pc);
|
||||
#else
|
||||
# define usbhuvcPrintProbeCommit(pc) do {} while(0)
|
||||
#endif
|
||||
bool usbhuvcProbe(USBHUVCDriver *uvcdp);
|
||||
bool usbhuvcCommit(USBHUVCDriver *uvcdp);
|
||||
void usbhuvcResetPC(USBHUVCDriver *uvcdp);
|
||||
static inline const usbh_uvc_ctrl_vs_probecommit_data_t *usbhuvcGetPCMin(USBHUVCDriver *uvcdp) {
|
||||
return &uvcdp->pc_min;
|
||||
}
|
||||
static inline const usbh_uvc_ctrl_vs_probecommit_data_t *usbhuvcGetPCMax(USBHUVCDriver *uvcdp) {
|
||||
return &uvcdp->pc_min;
|
||||
}
|
||||
static inline usbh_uvc_ctrl_vs_probecommit_data_t *usbhuvcGetPC(USBHUVCDriver *uvcdp) {
|
||||
return &uvcdp->pc;
|
||||
}
|
||||
|
||||
bool usbhuvcStreamStart(USBHUVCDriver *uvcdp, uint16_t min_ep_sz);
|
||||
bool usbhuvcStreamStop(USBHUVCDriver *uvcdp);
|
||||
|
||||
static inline msg_t usbhuvcLockAndFetchS(USBHUVCDriver *uvcdp, msg_t *msg, systime_t timeout) {
|
||||
chMtxLockS(&uvcdp->mtx);
|
||||
msg_t ret = chMBFetchS(&uvcdp->mb, msg, timeout);
|
||||
if (ret != MSG_OK)
|
||||
chMtxUnlockS(&uvcdp->mtx);
|
||||
return ret;
|
||||
}
|
||||
static inline msg_t usbhuvcLockAndFetch(USBHUVCDriver *uvcdp, msg_t *msg, systime_t timeout) {
|
||||
osalSysLock();
|
||||
msg_t ret = usbhuvcLockAndFetchS(uvcdp, msg, timeout);
|
||||
osalSysUnlock();
|
||||
return ret;
|
||||
}
|
||||
static inline void usbhuvcUnlock(USBHUVCDriver *uvcdp) {
|
||||
chMtxUnlock(&uvcdp->mtx);
|
||||
}
|
||||
static inline void usbhuvcFreeDataMessage(USBHUVCDriver *uvcdp, usbhuvc_message_data_t *msg) {
|
||||
chPoolFree(&uvcdp->mp_data, msg);
|
||||
}
|
||||
static inline void usbhuvcFreeStatusMessage(USBHUVCDriver *uvcdp, usbhuvc_message_status_t *msg) {
|
||||
chPoolFree(&uvcdp->mp_status, msg);
|
||||
}
|
||||
|
||||
|
||||
/* global initializer */
|
||||
void usbhuvcInit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* USBH_INCLUDE_USBH_UVC_H_ */
|
|
@ -28,6 +28,7 @@
|
|||
#include "usbh/dev/ftdi.h"
|
||||
#include "usbh/dev/msd.h"
|
||||
#include "usbh/dev/hid.h"
|
||||
#include "usbh/dev/uvc.h"
|
||||
|
||||
#if USBH_DEBUG_ENABLE_TRACE
|
||||
#define udbgf(f, ...) usbDbgPrintf(f, ##__VA_ARGS__)
|
||||
|
@ -125,6 +126,9 @@ void usbhInit(void) {
|
|||
#if HAL_USBH_USE_HID
|
||||
usbhhidInit();
|
||||
#endif
|
||||
#if HAL_USBH_USE_UVC
|
||||
usbhuvcInit();
|
||||
#endif
|
||||
#if HAL_USBH_USE_HUB
|
||||
usbhhubInit();
|
||||
#endif
|
||||
|
@ -421,7 +425,8 @@ usbh_urbstatus_t usbhControlRequestExtended(usbh_device_t *dev,
|
|||
uint32_t *actual_len,
|
||||
systime_t timeout) {
|
||||
|
||||
_check_dev(dev);
|
||||
if (!dev) return USBH_URBSTATUS_DISCONNECTED;
|
||||
|
||||
osalDbgCheck(req != NULL);
|
||||
|
||||
usbh_urb_t urb;
|
||||
|
@ -1310,6 +1315,9 @@ static const usbh_classdriverinfo_t *usbh_classdrivers_lookup[] = {
|
|||
#if HAL_USBH_USE_HID
|
||||
&usbhhidClassDriverInfo,
|
||||
#endif
|
||||
#if HAL_USBH_USE_UVC
|
||||
&usbhuvcClassDriverInfo,
|
||||
#endif
|
||||
#if HAL_USBH_USE_HUB
|
||||
&usbhhubClassDriverInfo,
|
||||
#endif
|
||||
|
@ -1341,7 +1349,7 @@ static bool _classdriver_load(usbh_device_t *dev, uint8_t class,
|
|||
#if HAL_USBH_USE_IAD
|
||||
/* special case: */
|
||||
if (info == &usbhiadClassDriverInfo)
|
||||
return HAL_SUCCESS;
|
||||
goto success; //return HAL_SUCCESS;
|
||||
#endif
|
||||
|
||||
if (drv != NULL)
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
/*
|
||||
ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio
|
||||
Copyright (C) 2015..2017 Diego Ismirlian, (dismirlian (at) google's mail)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
* usbh_uvc.c
|
||||
*
|
||||
* Created on: 14 de set. de 2015
|
||||
* Author: Diego Ismirlian (dismirlian (at) google's mail (dot) com)
|
||||
*
|
||||
* License:
|
||||
*
|
||||
* This file is free for non-commercial use until the company I work for decides what to do.
|
||||
* If in doubt, please contact me.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hal.h"
|
||||
#include "hal_usbh.h"
|
||||
|
||||
#if HAL_USBH_USE_UVC
|
||||
|
||||
|
@ -28,6 +23,10 @@
|
|||
#error "USBHUVC needs HAL_USBH_USE_IAD"
|
||||
#endif
|
||||
|
||||
#include "usbh/dev/uvc.h"
|
||||
#include "usbh/internal.h"
|
||||
#include <string.h>
|
||||
|
||||
#if USBHUVC_DEBUG_ENABLE_TRACE
|
||||
#define udbgf(f, ...) usbDbgPrintf(f, ##__VA_ARGS__)
|
||||
#define udbg(f, ...) usbDbgPuts(f, ##__VA_ARGS__)
|
||||
|
@ -61,6 +60,9 @@
|
|||
#endif
|
||||
|
||||
|
||||
USBHUVCDriver USBHUVCD[HAL_USBHUVC_MAX_INSTANCES];
|
||||
|
||||
|
||||
static usbh_baseclassdriver_t *uvc_load(usbh_device_t *dev,
|
||||
const uint8_t *descriptor, uint16_t rem);
|
||||
static void uvc_unload(usbh_baseclassdriver_t *drv);
|
||||
|
@ -73,16 +75,653 @@ const usbh_classdriverinfo_t usbhuvcClassDriverInfo = {
|
|||
0x0e, 0x03, 0x00, "UVC", &class_driver_vmt
|
||||
};
|
||||
|
||||
static bool _request(USBHUVCDriver *uvcdp,
|
||||
uint8_t bRequest, uint8_t entity, uint8_t control,
|
||||
uint16_t wLength, uint8_t *data, uint8_t interf) {
|
||||
|
||||
usbh_urbstatus_t res;
|
||||
|
||||
if (bRequest & 0x80) {
|
||||
res = usbhControlRequest(uvcdp->dev,
|
||||
USBH_REQTYPE_CLASSIN(USBH_REQTYPE_RECIP_INTERFACE),
|
||||
bRequest,
|
||||
((control) << 8),
|
||||
(interf) | ((entity) << 8),
|
||||
wLength, data);
|
||||
} else {
|
||||
res = usbhControlRequest(uvcdp->dev,
|
||||
USBH_REQTYPE_CLASSOUT(USBH_REQTYPE_RECIP_INTERFACE),
|
||||
bRequest,
|
||||
((control) << 8),
|
||||
(interf) | ((entity) << 8),
|
||||
wLength, data);
|
||||
}
|
||||
|
||||
if (res != USBH_URBSTATUS_OK)
|
||||
return HAL_FAILED;
|
||||
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
bool usbhuvcVCRequest(USBHUVCDriver *uvcdp,
|
||||
uint8_t bRequest, uint8_t entity, uint8_t control,
|
||||
uint16_t wLength, uint8_t *data) {
|
||||
return _request(uvcdp, bRequest, entity, control, wLength, data, if_get(&uvcdp->ivc)->bInterfaceNumber);
|
||||
}
|
||||
|
||||
bool usbhuvcVSRequest(USBHUVCDriver *uvcdp,
|
||||
uint8_t bRequest, uint8_t control,
|
||||
uint16_t wLength, uint8_t *data) {
|
||||
|
||||
return _request(uvcdp, bRequest, 0, control, wLength, data, if_get(&uvcdp->ivs)->bInterfaceNumber);
|
||||
}
|
||||
|
||||
static bool _set_vs_alternate(USBHUVCDriver *uvcdp, uint16_t min_ep_size) {
|
||||
|
||||
if (min_ep_size == 0) {
|
||||
uinfo("Selecting Alternate setting 0");
|
||||
return usbhStdReqSetInterface(uvcdp->dev, if_get(&uvcdp->ivs)->bInterfaceNumber, 0);
|
||||
}
|
||||
|
||||
if_iterator_t iif = uvcdp->ivs;
|
||||
generic_iterator_t iep;
|
||||
const usbh_endpoint_descriptor_t *ep = NULL;
|
||||
uint8_t alt = 0;
|
||||
uint16_t sz = 0xffff;
|
||||
|
||||
uinfof("Searching alternate setting with min_ep_size=%d", min_ep_size);
|
||||
|
||||
for (; iif.valid; if_iter_next(&iif)) {
|
||||
const usbh_interface_descriptor_t *const ifdesc = if_get(&iif);
|
||||
|
||||
if ((ifdesc->bInterfaceClass != UVC_CC_VIDEO)
|
||||
|| (ifdesc->bInterfaceSubClass != UVC_SC_VIDEOSTREAMING))
|
||||
continue;
|
||||
|
||||
uinfof("\tScanning alternate setting=%d", ifdesc->bAlternateSetting);
|
||||
|
||||
if (ifdesc->bNumEndpoints == 0)
|
||||
continue;
|
||||
|
||||
for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) {
|
||||
const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep);
|
||||
if (((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_ISO)
|
||||
&& ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) {
|
||||
|
||||
uinfof("\t Endpoint wMaxPacketSize = %d", epdesc->wMaxPacketSize);
|
||||
|
||||
if (epdesc->wMaxPacketSize >= min_ep_size) {
|
||||
if (epdesc->wMaxPacketSize < sz) {
|
||||
uinfo("\t Found new optimal alternate setting");
|
||||
sz = epdesc->wMaxPacketSize;
|
||||
alt = ifdesc->bAlternateSetting;
|
||||
ep = epdesc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ep && alt) {
|
||||
uinfof("\tSelecting Alternate setting %d", alt);
|
||||
if (usbhStdReqSetInterface(uvcdp->dev, if_get(&uvcdp->ivs)->bInterfaceNumber, alt) == HAL_SUCCESS) {
|
||||
usbhEPObjectInit(&uvcdp->ep_iso, uvcdp->dev, ep);
|
||||
usbhEPSetName(&uvcdp->ep_iso, "UVC[ISO ]");
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return HAL_FAILED;
|
||||
}
|
||||
|
||||
#if USBH_DEBUG_ENABLE && USBHUVC_DEBUG_ENABLE_INFO
|
||||
void usbhuvcPrintProbeCommit(const usbh_uvc_ctrl_vs_probecommit_data_t *pc) {
|
||||
|
||||
//uinfof("UVC: probe/commit data:");
|
||||
uinfof("\tbmHint=%04x", pc->bmHint);
|
||||
uinfof("\tbFormatIndex=%d, bFrameIndex=%d, dwFrameInterval=%u",
|
||||
pc->bFormatIndex, pc->bFrameIndex, pc->dwFrameInterval);
|
||||
uinfof("\twKeyFrameRate=%d, wPFrameRate=%d, wCompQuality=%u, wCompWindowSize=%u",
|
||||
pc->wKeyFrameRate, pc->wPFrameRate, pc->wCompQuality, pc->wCompWindowSize);
|
||||
uinfof("\twDelay=%d", pc->wDelay);
|
||||
uinfof("\tdwMaxVideoFrameSize=%u", pc->dwMaxVideoFrameSize);
|
||||
uinfof("\tdwMaxPayloadTransferSize=%u", pc->dwMaxPayloadTransferSize);
|
||||
/* uinfof("\tdwClockFrequency=%u", pc->dwClockFrequency);
|
||||
uinfof("\tbmFramingInfo=%02x", pc->bmFramingInfo);
|
||||
uinfof("\tbPreferedVersion=%d, bMinVersion=%d, bMaxVersion=%d",
|
||||
pc->bPreferedVersion, pc->bMinVersion, pc->bMaxVersion); */
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _post(USBHUVCDriver *uvcdp, usbh_urb_t *urb, memory_pool_t *mp, uint16_t type) {
|
||||
usbhuvc_message_base_t *const msg = (usbhuvc_message_base_t *)urb->buff - 1;
|
||||
msg->timestamp = osalOsGetSystemTimeX();
|
||||
|
||||
usbhuvc_message_base_t *const new_msg = (usbhuvc_message_base_t *)chPoolAllocI(mp);
|
||||
if (new_msg != NULL) {
|
||||
/* allocated the new buffer, now try to post the message to the mailbox */
|
||||
if (chMBPostI(&uvcdp->mb, (msg_t)msg) == MSG_OK) {
|
||||
/* everything OK, complete the missing fields */
|
||||
msg->type = type;
|
||||
msg->length = urb->actualLength;
|
||||
|
||||
/* change the URB's buffer to the newly allocated one */
|
||||
urb->buff = (uint8_t *)(new_msg + 1);
|
||||
} else {
|
||||
/* couldn't post the message, free the newly allocated buffer */
|
||||
uerr("UVC: error, mailbox overrun");
|
||||
chPoolFreeI(&uvcdp->mp_status, new_msg);
|
||||
}
|
||||
} else {
|
||||
uerrf("UVC: error, %s pool overrun", mp == &uvcdp->mp_data ? "data" : "status");
|
||||
}
|
||||
}
|
||||
|
||||
static void _cb_int(usbh_urb_t *urb) {
|
||||
USBHUVCDriver *uvcdp = (USBHUVCDriver *)urb->userData;
|
||||
|
||||
switch (urb->status) {
|
||||
case USBH_URBSTATUS_OK:
|
||||
if (urb->actualLength >= 2) {
|
||||
_post(uvcdp, urb, &uvcdp->mp_status, USBHUVC_MESSAGETYPE_STATUS);
|
||||
} else {
|
||||
uerrf("UVC: INT IN, actualLength=%d", urb->actualLength);
|
||||
}
|
||||
break;
|
||||
case USBH_URBSTATUS_TIMEOUT: /* the device NAKed */
|
||||
udbg("UVC: INT IN no info");
|
||||
break;
|
||||
case USBH_URBSTATUS_DISCONNECTED:
|
||||
case USBH_URBSTATUS_CANCELLED:
|
||||
uwarn("UVC: INT IN status = DISCONNECTED/CANCELLED, aborting");
|
||||
return;
|
||||
default:
|
||||
uerrf("UVC: INT IN error, unexpected status = %d", urb->status);
|
||||
break;
|
||||
}
|
||||
|
||||
usbhURBObjectResetI(urb);
|
||||
usbhURBSubmitI(urb);
|
||||
}
|
||||
|
||||
static void _cb_iso(usbh_urb_t *urb) {
|
||||
USBHUVCDriver *uvcdp = (USBHUVCDriver *)urb->userData;
|
||||
|
||||
if ((urb->status == USBH_URBSTATUS_DISCONNECTED)
|
||||
|| (urb->status == USBH_URBSTATUS_CANCELLED)) {
|
||||
uwarn("UVC: ISO IN status = DISCONNECTED/CANCELLED, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
if (urb->status != USBH_URBSTATUS_OK) {
|
||||
uerrf("UVC: ISO IN error, unexpected status = %d", urb->status);
|
||||
} else if (urb->actualLength >= 2) {
|
||||
const uint8_t *const buff = (const uint8_t *)urb->buff;
|
||||
if (buff[0] < 2) {
|
||||
uerrf("UVC: ISO IN, bHeaderLength=%d", buff[0]);
|
||||
} else if (buff[0] > urb->actualLength) {
|
||||
uerrf("UVC: ISO IN, bHeaderLength=%d > actualLength=%d", buff[0], urb->actualLength);
|
||||
} else {
|
||||
udbgf("UVC: ISO IN len=%d, hdr=%d, FID=%d, EOF=%d, ERR=%d, EOH=%d",
|
||||
urb->actualLength,
|
||||
buff[0],
|
||||
buff[1] & UVC_HDR_FID,
|
||||
buff[1] & UVC_HDR_EOF,
|
||||
buff[1] & UVC_HDR_ERR,
|
||||
buff[1] & UVC_HDR_EOH);
|
||||
|
||||
if ((urb->actualLength > buff[0])
|
||||
|| (buff[1] & (UVC_HDR_EOF | UVC_HDR_ERR))) {
|
||||
_post(uvcdp, urb, &uvcdp->mp_data, USBHUVC_MESSAGETYPE_DATA);
|
||||
} else {
|
||||
udbgf("UVC: ISO IN skip: len=%d, hdr=%d, FID=%d, EOF=%d, ERR=%d, EOH=%d",
|
||||
urb->actualLength,
|
||||
buff[0],
|
||||
buff[1] & UVC_HDR_FID,
|
||||
buff[1] & UVC_HDR_EOF,
|
||||
buff[1] & UVC_HDR_ERR,
|
||||
buff[1] & UVC_HDR_EOH);
|
||||
}
|
||||
}
|
||||
} else if (urb->actualLength > 0) {
|
||||
uerrf("UVC: ISO IN, actualLength=%d", urb->actualLength);
|
||||
}
|
||||
|
||||
usbhURBObjectResetI(urb);
|
||||
usbhURBSubmitI(urb);
|
||||
}
|
||||
|
||||
|
||||
bool usbhuvcStreamStart(USBHUVCDriver *uvcdp, uint16_t min_ep_sz) {
|
||||
bool ret = HAL_FAILED;
|
||||
osalSysLock();
|
||||
osalDbgCheck(uvcdp && (uvcdp->state != USBHUVC_STATE_UNINITIALIZED) &&
|
||||
(uvcdp->state != USBHUVC_STATE_BUSY));
|
||||
if (uvcdp->state == USBHUVC_STATE_STREAMING) {
|
||||
osalSysUnlock();
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
if (uvcdp->state != USBHUVC_STATE_READY) {
|
||||
osalSysUnlock();
|
||||
return HAL_FAILED;
|
||||
}
|
||||
uvcdp->state = USBHUVC_STATE_BUSY;
|
||||
osalSysUnlock();
|
||||
|
||||
//set the alternate setting
|
||||
if (_set_vs_alternate(uvcdp, min_ep_sz) != HAL_SUCCESS)
|
||||
goto exit;
|
||||
|
||||
//reserve working RAM
|
||||
uint32_t datapackets;
|
||||
uint32_t data_sz = (uvcdp->ep_iso.wMaxPacketSize + sizeof(usbhuvc_message_data_t) + 3) & ~3;
|
||||
|
||||
datapackets = HAL_USBHUVC_WORK_RAM_SIZE / data_sz;
|
||||
if (datapackets == 0) {
|
||||
uerr("Not enough work RAM");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
uint32_t workramsz = datapackets * data_sz;
|
||||
uinfof("Reserving %u bytes of RAM (%d data packets of %d bytes)", workramsz, datapackets, data_sz);
|
||||
if (datapackets > (HAL_USBHUVC_MAX_MAILBOX_SZ - HAL_USBHUVC_STATUS_PACKETS_COUNT)) {
|
||||
uwarn("Mailbox may overflow, use a larger HAL_USBHUVC_MAX_MAILBOX_SZ. UVC will under-utilize the assigned work RAM.");
|
||||
}
|
||||
chMBResumeX(&uvcdp->mb);
|
||||
|
||||
uvcdp->mp_data_buffer = chHeapAlloc(NULL, workramsz);
|
||||
if (uvcdp->mp_data_buffer == NULL) {
|
||||
uerr("Couldn't reserve RAM");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
//initialize the mempool
|
||||
const uint8_t *elem = (const uint8_t *)uvcdp->mp_data_buffer;
|
||||
chPoolObjectInit(&uvcdp->mp_data, data_sz, NULL);
|
||||
while (datapackets--) {
|
||||
chPoolFree(&uvcdp->mp_data, (void *)elem);
|
||||
elem += data_sz;
|
||||
}
|
||||
|
||||
//open the endpoint
|
||||
usbhEPOpen(&uvcdp->ep_iso);
|
||||
|
||||
//allocate 1 buffer and submit the first transfer
|
||||
usbhuvc_message_data_t *const msg = (usbhuvc_message_data_t *)chPoolAlloc(&uvcdp->mp_data);
|
||||
osalDbgCheck(msg);
|
||||
usbhURBObjectInit(&uvcdp->urb_iso, &uvcdp->ep_iso, _cb_iso, uvcdp, msg->data, uvcdp->ep_iso.wMaxPacketSize);
|
||||
osalSysLock();
|
||||
usbhURBSubmitI(&uvcdp->urb_iso);
|
||||
osalOsRescheduleS();
|
||||
osalSysUnlock();
|
||||
|
||||
ret = HAL_SUCCESS;
|
||||
goto exit;
|
||||
|
||||
failed:
|
||||
_set_vs_alternate(uvcdp, 0);
|
||||
if (uvcdp->mp_data_buffer)
|
||||
chHeapFree(uvcdp->mp_data_buffer);
|
||||
|
||||
exit:
|
||||
osalSysLock();
|
||||
if (ret == HAL_SUCCESS)
|
||||
uvcdp->state = USBHUVC_STATE_STREAMING;
|
||||
else
|
||||
uvcdp->state = USBHUVC_STATE_READY;
|
||||
osalSysUnlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool usbhuvcStreamStop(USBHUVCDriver *uvcdp) {
|
||||
osalSysLock();
|
||||
osalDbgCheck(uvcdp && (uvcdp->state != USBHUVC_STATE_UNINITIALIZED) &&
|
||||
(uvcdp->state != USBHUVC_STATE_BUSY));
|
||||
if (uvcdp->state != USBHUVC_STATE_STREAMING) {
|
||||
osalSysUnlock();
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
uvcdp->state = USBHUVC_STATE_BUSY;
|
||||
|
||||
//close the ISO endpoint
|
||||
usbhEPCloseS(&uvcdp->ep_iso);
|
||||
|
||||
//purge the mailbox
|
||||
chMBResetI(&uvcdp->mb); //TODO: the status messages are lost!!
|
||||
chMtxLockS(&uvcdp->mtx);
|
||||
osalSysUnlock();
|
||||
|
||||
//free the working memory
|
||||
chHeapFree(uvcdp->mp_data_buffer);
|
||||
uvcdp->mp_data_buffer = 0;
|
||||
|
||||
//set alternate setting to 0
|
||||
_set_vs_alternate(uvcdp, 0);
|
||||
|
||||
osalSysLock();
|
||||
uvcdp->state = USBHUVC_STATE_READY;
|
||||
chMtxUnlockS(&uvcdp->mtx);
|
||||
osalSysUnlock();
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
bool usbhuvcFindVSDescriptor(USBHUVCDriver *uvcdp,
|
||||
generic_iterator_t *ics,
|
||||
uint8_t bDescriptorSubtype,
|
||||
bool start) {
|
||||
|
||||
if (start)
|
||||
cs_iter_init(ics, (generic_iterator_t *)&uvcdp->ivs);
|
||||
else
|
||||
cs_iter_next(ics);
|
||||
|
||||
for (; ics->valid; cs_iter_next(ics)) {
|
||||
if (ics->curr[1] != UVC_CS_INTERFACE)
|
||||
break;
|
||||
if (ics->curr[2] == bDescriptorSubtype)
|
||||
return HAL_SUCCESS;
|
||||
if (!start)
|
||||
break;
|
||||
}
|
||||
return HAL_FAILED;
|
||||
}
|
||||
|
||||
void usbhuvcResetPC(USBHUVCDriver *uvcdp) {
|
||||
memset(&uvcdp->pc, 0, sizeof(uvcdp->pc));
|
||||
}
|
||||
|
||||
bool usbhuvcProbe(USBHUVCDriver *uvcdp) {
|
||||
// memset(&uvcdp->pc_min, 0, sizeof(uvcdp->pc_min));
|
||||
// memset(&uvcdp->pc_max, 0, sizeof(uvcdp->pc_max));
|
||||
|
||||
if (usbhuvcVSRequest(uvcdp, UVC_SET_CUR, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS)
|
||||
return HAL_FAILED;
|
||||
if (usbhuvcVSRequest(uvcdp, UVC_GET_CUR, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS)
|
||||
return HAL_FAILED;
|
||||
if (usbhuvcVSRequest(uvcdp, UVC_GET_MAX, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc_max), (uint8_t *)&uvcdp->pc_max) != HAL_SUCCESS)
|
||||
return HAL_FAILED;
|
||||
if (usbhuvcVSRequest(uvcdp, UVC_GET_MIN, UVC_CTRL_VS_PROBE_CONTROL, sizeof(uvcdp->pc_min), (uint8_t *)&uvcdp->pc_min) != HAL_SUCCESS)
|
||||
return HAL_FAILED;
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
bool usbhuvcCommit(USBHUVCDriver *uvcdp) {
|
||||
if (usbhuvcVSRequest(uvcdp, UVC_SET_CUR, UVC_CTRL_VS_COMMIT_CONTROL, sizeof(uvcdp->pc), (uint8_t *)&uvcdp->pc) != HAL_SUCCESS)
|
||||
return HAL_FAILED;
|
||||
|
||||
osalSysLock();
|
||||
if (uvcdp->state == USBHUVC_STATE_ACTIVE)
|
||||
uvcdp->state = USBHUVC_STATE_READY;
|
||||
osalSysUnlock();
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
uint32_t usbhuvcEstimateRequiredEPSize(USBHUVCDriver *uvcdp, const uint8_t *formatdesc,
|
||||
const uint8_t *framedesc, uint32_t dwFrameInterval) {
|
||||
|
||||
osalDbgCheck(framedesc);
|
||||
osalDbgCheck(framedesc[0] > 3);
|
||||
osalDbgCheck(framedesc[1] == UVC_CS_INTERFACE);
|
||||
osalDbgCheck(formatdesc);
|
||||
osalDbgCheck(formatdesc[0] > 3);
|
||||
osalDbgCheck(formatdesc[1] == UVC_CS_INTERFACE);
|
||||
|
||||
uint16_t w, h, div, mul;
|
||||
uint8_t bpp;
|
||||
|
||||
switch (framedesc[2]) {
|
||||
case UVC_VS_FRAME_MJPEG: {
|
||||
const usbh_uvc_frame_mjpeg_t *frame = (const usbh_uvc_frame_mjpeg_t *)framedesc;
|
||||
//const usbh_uvc_format_mjpeg_t *fmt = (const usbh_uvc_format_mjpeg_t *)formatdesc;
|
||||
w = frame->wWidth;
|
||||
h = frame->wHeight;
|
||||
bpp = 16; //TODO: check this!!
|
||||
mul = 1;
|
||||
div = 5; //TODO: check this estimate
|
||||
} break;
|
||||
case UVC_VS_FRAME_UNCOMPRESSED: {
|
||||
const usbh_uvc_frame_uncompressed_t *frame = (const usbh_uvc_frame_uncompressed_t *)framedesc;
|
||||
const usbh_uvc_format_uncompressed *fmt = (const usbh_uvc_format_uncompressed *)formatdesc;
|
||||
w = frame->wWidth;
|
||||
h = frame->wHeight;
|
||||
bpp = fmt->bBitsPerPixel;
|
||||
mul = div = 1;
|
||||
} break;
|
||||
default:
|
||||
uwarn("Unsupported format");
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
uint32_t sz = w * h / 8 * bpp;
|
||||
sz *= 10000000UL / dwFrameInterval;
|
||||
sz /= 1000;
|
||||
|
||||
if (uvcdp->dev->speed == USBH_DEVSPEED_HIGH)
|
||||
div *= 8;
|
||||
|
||||
return (sz * mul) / div + 12;
|
||||
}
|
||||
|
||||
void usbhuvcObjectInit(USBHUVCDriver *uvcdp) {
|
||||
osalDbgCheck(uvcdp != NULL);
|
||||
memset(uvcdp, 0, sizeof(*uvcdp));
|
||||
uvcdp->info = &usbhuvcClassDriverInfo;
|
||||
chMBObjectInit(&uvcdp->mb, uvcdp->mb_buff, HAL_USBHUVC_MAX_MAILBOX_SZ);
|
||||
chMtxObjectInit(&uvcdp->mtx);
|
||||
uvcdp->state = USBHUVC_STATE_STOP;
|
||||
}
|
||||
|
||||
|
||||
static usbh_baseclassdriver_t *uvc_load(usbh_device_t *dev, const uint8_t *descriptor, uint16_t rem) {
|
||||
(void)dev;
|
||||
(void)descriptor;
|
||||
(void)rem;
|
||||
|
||||
USBHUVCDriver *uvcdp;
|
||||
uint8_t i;
|
||||
|
||||
if (descriptor[1] != USBH_DT_INTERFACE_ASSOCIATION)
|
||||
return NULL;
|
||||
|
||||
/* alloc driver */
|
||||
for (i = 0; i < HAL_USBHUVC_MAX_INSTANCES; i++) {
|
||||
if (USBHUVCD[i].dev == NULL) {
|
||||
uvcdp = &USBHUVCD[i];
|
||||
goto alloc_ok;
|
||||
}
|
||||
}
|
||||
|
||||
uwarn("Can't alloc UVC driver");
|
||||
|
||||
/* can't alloc */
|
||||
return NULL;
|
||||
|
||||
alloc_ok:
|
||||
/* initialize the driver's variables */
|
||||
uvcdp->ivc.curr = uvcdp->ivs.curr = NULL;
|
||||
|
||||
usbhEPSetName(&dev->ctrl, "UVC[CTRL]");
|
||||
|
||||
const usbh_ia_descriptor_t *iad = (const usbh_ia_descriptor_t *)descriptor;
|
||||
if_iterator_t iif;
|
||||
generic_iterator_t ics;
|
||||
generic_iterator_t iep;
|
||||
|
||||
iif.iad = iad;
|
||||
iif.curr = descriptor;
|
||||
iif.rem = rem;
|
||||
|
||||
for (if_iter_next(&iif); iif.valid; if_iter_next(&iif)) {
|
||||
if (iif.iad != iad) break;
|
||||
|
||||
const usbh_interface_descriptor_t *const ifdesc = if_get(&iif);
|
||||
if (ifdesc->bInterfaceClass != UVC_CC_VIDEO) {
|
||||
uwarnf("Skipping Interface %d (class != UVC_CC_VIDEO)",
|
||||
ifdesc->bInterfaceNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
uinfof("Interface %d, Alt=%d, Class=UVC_CC_VIDEO, Subclass=%02x",
|
||||
ifdesc->bInterfaceNumber,
|
||||
ifdesc->bAlternateSetting,
|
||||
ifdesc->bInterfaceSubClass);
|
||||
|
||||
switch (ifdesc->bInterfaceSubClass) {
|
||||
case UVC_SC_VIDEOCONTROL:
|
||||
if (uvcdp->ivc.curr == NULL) {
|
||||
uvcdp->ivc = iif;
|
||||
}
|
||||
for (cs_iter_init(&ics, (generic_iterator_t *)&iif); ics.valid; cs_iter_next(&ics)) {
|
||||
if (ics.curr[1] != UVC_CS_INTERFACE) {
|
||||
uwarnf("Unknown descriptor=%02X", ics.curr[1]);
|
||||
continue;
|
||||
}
|
||||
switch (ics.curr[2]) {
|
||||
case UVC_VC_HEADER:
|
||||
uinfo(" VC_HEADER"); break;
|
||||
case UVC_VC_INPUT_TERMINAL:
|
||||
uinfof(" VC_INPUT_TERMINAL, ID=%d", ics.curr[3]); break;
|
||||
case UVC_VC_OUTPUT_TERMINAL:
|
||||
uinfof(" VC_OUTPUT_TERMINAL, ID=%d", ics.curr[3]); break;
|
||||
case UVC_VC_SELECTOR_UNIT:
|
||||
uinfof(" VC_SELECTOR_UNIT, ID=%d", ics.curr[3]); break;
|
||||
case UVC_VC_PROCESSING_UNIT:
|
||||
uinfof(" VC_PROCESSING_UNIT, ID=%d", ics.curr[3]); break;
|
||||
case UVC_VC_EXTENSION_UNIT:
|
||||
uinfof(" VC_EXTENSION_UNIT, ID=%d", ics.curr[3]); break;
|
||||
default:
|
||||
uwarnf("Unknown video bDescriptorSubtype=%02x", ics.curr[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UVC_SC_VIDEOSTREAMING:
|
||||
if (uvcdp->ivs.curr == NULL) {
|
||||
uvcdp->ivs = iif;
|
||||
}
|
||||
for (cs_iter_init(&ics, (generic_iterator_t *)&iif); ics.valid; cs_iter_next(&ics)) {
|
||||
if (ics.curr[1] != UVC_CS_INTERFACE) {
|
||||
uwarnf("Unknown descriptor=%02X", ics.curr[1]);
|
||||
continue;
|
||||
}
|
||||
switch (ics.curr[2]) {
|
||||
case UVC_VS_INPUT_HEADER:
|
||||
uinfo(" VS_INPUT_HEADER"); break;
|
||||
case UVC_VS_OUTPUT_HEADER:
|
||||
uinfo(" VS_OUTPUT_HEADER"); break;
|
||||
case UVC_VS_STILL_IMAGE_FRAME:
|
||||
uinfo(" VS_STILL_IMAGE_FRAME"); break;
|
||||
|
||||
case UVC_VS_FORMAT_UNCOMPRESSED:
|
||||
uinfof(" VS_FORMAT_UNCOMPRESSED, bFormatIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FORMAT_MPEG2TS:
|
||||
uinfof(" VS_FORMAT_MPEG2TS, bFormatIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FORMAT_DV:
|
||||
uinfof(" VS_FORMAT_DV, bFormatIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FORMAT_MJPEG:
|
||||
uinfof(" VS_FORMAT_MJPEG, bFormatIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FORMAT_FRAME_BASED:
|
||||
uinfof(" VS_FORMAT_FRAME_BASED, bFormatIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FORMAT_STREAM_BASED:
|
||||
uinfof(" VS_FORMAT_STREAM_BASED, bFormatIndex=%d", ics.curr[3]); break;
|
||||
|
||||
case UVC_VS_FRAME_UNCOMPRESSED:
|
||||
uinfof(" VS_FRAME_UNCOMPRESSED, bFrameIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FRAME_MJPEG:
|
||||
uinfof(" VS_FRAME_MJPEG, bFrameIndex=%d", ics.curr[3]); break;
|
||||
case UVC_VS_FRAME_FRAME_BASED:
|
||||
uinfof(" VS_FRAME_FRAME_BASED, bFrameIndex=%d", ics.curr[3]); break;
|
||||
|
||||
case UVC_VS_COLOR_FORMAT:
|
||||
uinfo(" VS_COLOR_FORMAT"); break;
|
||||
default:
|
||||
uwarnf("Unknown video bDescriptorSubtype=%02x", ics.curr[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
uwarnf("Unknown video bInterfaceSubClass=%02x", ifdesc->bInterfaceSubClass);
|
||||
break;
|
||||
}
|
||||
|
||||
for (ep_iter_init(&iep, &iif); iep.valid; ep_iter_next(&iep)) {
|
||||
const usbh_endpoint_descriptor_t *const epdesc = ep_get(&iep);
|
||||
|
||||
if ((ifdesc->bInterfaceSubClass == UVC_SC_VIDEOCONTROL)
|
||||
&& ((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_INT)
|
||||
&& ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) {
|
||||
/* found VC interrupt endpoint */
|
||||
uinfof(" VC Interrupt endpoint; %02x, bInterval=%d",
|
||||
epdesc->bEndpointAddress, epdesc->bInterval);
|
||||
usbhEPObjectInit(&uvcdp->ep_int, dev, epdesc);
|
||||
usbhEPSetName(&uvcdp->ep_int, "UVC[INT ]");
|
||||
} else if ((ifdesc->bInterfaceSubClass == UVC_SC_VIDEOSTREAMING)
|
||||
&& ((epdesc->bmAttributes & 0x03) == USBH_EPTYPE_ISO)
|
||||
&& ((epdesc->bEndpointAddress & 0x80) == USBH_EPDIR_IN)) {
|
||||
/* found VS isochronous endpoint */
|
||||
uinfof(" VS Isochronous endpoint; %02x, bInterval=%d, bmAttributes=%02x",
|
||||
epdesc->bEndpointAddress, epdesc->bInterval, epdesc->bmAttributes);
|
||||
} else {
|
||||
/* unknown EP */
|
||||
uwarnf(" <unknown endpoint>, bEndpointAddress=%02x, bmAttributes=%02x",
|
||||
epdesc->bEndpointAddress, epdesc->bmAttributes);
|
||||
}
|
||||
|
||||
for (cs_iter_init(&ics, &iep); ics.valid; cs_iter_next(&ics)) {
|
||||
uinfof(" CS_ENDPOINT bLength=%d, bDescriptorType=%02X",
|
||||
ics.curr[0], ics.curr[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((uvcdp->ivc.curr == NULL) || (uvcdp->ivs.curr == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// uvcdp->dev = dev;
|
||||
|
||||
_set_vs_alternate(uvcdp, 0);
|
||||
|
||||
/* initialize the INT endpoint */
|
||||
chPoolObjectInit(&uvcdp->mp_status, sizeof(usbhuvc_message_status_t), NULL);
|
||||
for(i = 0; i < HAL_USBHUVC_STATUS_PACKETS_COUNT; i++)
|
||||
chPoolFree(&uvcdp->mp_status, &uvcdp->mp_status_buffer[i]);
|
||||
|
||||
usbhEPOpen(&uvcdp->ep_int);
|
||||
|
||||
usbhuvc_message_status_t *const msg = (usbhuvc_message_status_t *)chPoolAlloc(&uvcdp->mp_status);
|
||||
osalDbgCheck(msg);
|
||||
usbhURBObjectInit(&uvcdp->urb_int, &uvcdp->ep_int, _cb_int, uvcdp, msg->data, USBHUVC_MAX_STATUS_PACKET_SZ);
|
||||
osalSysLock();
|
||||
usbhURBSubmitI(&uvcdp->urb_int);
|
||||
uvcdp->state = USBHUVC_STATE_ACTIVE;
|
||||
osalOsRescheduleS();
|
||||
osalSysUnlock();
|
||||
|
||||
dev->keepFullCfgDesc++;
|
||||
return (usbh_baseclassdriver_t *)uvcdp;
|
||||
}
|
||||
|
||||
static void uvc_unload(usbh_baseclassdriver_t *drv) {
|
||||
(void)drv;
|
||||
USBHUVCDriver *const uvcdp = (USBHUVCDriver *)drv;
|
||||
|
||||
usbhuvcStreamStop(uvcdp);
|
||||
|
||||
usbhEPClose(&uvcdp->ep_int);
|
||||
|
||||
//TODO: free
|
||||
|
||||
if (drv->dev->keepFullCfgDesc)
|
||||
drv->dev->keepFullCfgDesc--;
|
||||
|
||||
osalSysLock();
|
||||
uvcdp->state = USBHUVC_STATE_STOP;
|
||||
osalSysUnlock();
|
||||
}
|
||||
|
||||
void usbhuvcInit(void) {
|
||||
uint8_t i;
|
||||
for (i = 0; i < HAL_USBHUVC_MAX_INSTANCES; i++) {
|
||||
usbhuvcObjectInit(&USBHUVCD[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -107,14 +107,6 @@
|
|||
#define HAL_USBHFTDI_DEFAULT_XON 0x11
|
||||
#define HAL_USBHFTDI_DEFAULT_XOFF 0x13
|
||||
|
||||
/* UVC */
|
||||
#define HAL_USBH_USE_UVC FALSE
|
||||
|
||||
#define HAL_USBHUVC_MAX_INSTANCES 1
|
||||
#define HAL_USBHUVC_MAX_MAILBOX_SZ 70
|
||||
#define HAL_USBHUVC_WORK_RAM_SIZE 20000
|
||||
#define HAL_USBHUVC_STATUS_PACKETS_COUNT 10
|
||||
|
||||
/* AOA */
|
||||
#define HAL_USBH_USE_AOA TRUE
|
||||
|
||||
|
@ -130,19 +122,25 @@
|
|||
#define HAL_USBHAOA_DEFAULT_SERIAL NULL
|
||||
#define HAL_USBHAOA_DEFAULT_AUDIO_MODE USBHAOA_AUDIO_MODE_DISABLED
|
||||
|
||||
/* UVC */
|
||||
#define HAL_USBH_USE_UVC TRUE
|
||||
|
||||
#define HAL_USBHUVC_MAX_INSTANCES 1
|
||||
#define HAL_USBHUVC_MAX_MAILBOX_SZ 70
|
||||
#define HAL_USBHUVC_WORK_RAM_SIZE 20000
|
||||
#define HAL_USBHUVC_STATUS_PACKETS_COUNT 10
|
||||
|
||||
/* HID */
|
||||
#define HAL_USBH_USE_HID TRUE
|
||||
#define HAL_USBHHID_MAX_INSTANCES 2
|
||||
#define HAL_USBHHID_USE_INTERRUPT_OUT FALSE
|
||||
|
||||
|
||||
/* HUB */
|
||||
#define HAL_USBH_USE_HUB TRUE
|
||||
|
||||
#define HAL_USBHHUB_MAX_INSTANCES 1
|
||||
#define HAL_USBHHUB_MAX_PORTS 6
|
||||
|
||||
|
||||
/* debug */
|
||||
#define USBH_DEBUG_ENABLE TRUE
|
||||
#define USBH_DEBUG_USBHD USBHD1
|
||||
|
@ -184,7 +182,7 @@
|
|||
#define USBHAOA_DEBUG_ENABLE_WARNINGS TRUE
|
||||
#define USBHAOA_DEBUG_ENABLE_ERRORS TRUE
|
||||
|
||||
#define USBHHID_DEBUG_ENABLE_TRACE TRUE
|
||||
#define USBHHID_DEBUG_ENABLE_TRACE FALSE
|
||||
#define USBHHID_DEBUG_ENABLE_INFO TRUE
|
||||
#define USBHHID_DEBUG_ENABLE_WARNINGS TRUE
|
||||
#define USBHHID_DEBUG_ENABLE_ERRORS TRUE
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
#include "ff.h"
|
||||
#include <string.h>
|
||||
|
||||
#define UVC_TO_MSD_PHOTOS_CAPTURE TRUE
|
||||
|
||||
|
||||
#if HAL_USBH_USE_FTDI || HAL_USBH_USE_AOA
|
||||
static uint8_t buf[] =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
|
@ -288,6 +291,8 @@ start:
|
|||
#include "ff.h"
|
||||
|
||||
static FATFS MSDLUN0FS;
|
||||
|
||||
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
static uint8_t fbuff[10240];
|
||||
static FIL file;
|
||||
|
||||
|
@ -326,6 +331,8 @@ static FRESULT scan_files(BaseSequentialStream *chp, char *path) {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
static THD_WORKING_AREA(waTestMSD, 1024);
|
||||
static void ThreadTestMSD(void *p) {
|
||||
(void)p;
|
||||
|
@ -333,10 +340,12 @@ static void ThreadTestMSD(void *p) {
|
|||
FATFS *fsp;
|
||||
DWORD clusters;
|
||||
FRESULT res;
|
||||
BaseSequentialStream * const chp = (BaseSequentialStream *)&USBH_DEBUG_SD;
|
||||
blkstate_t state;
|
||||
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
BaseSequentialStream * const chp = (BaseSequentialStream *)&USBH_DEBUG_SD;
|
||||
systime_t st, et;
|
||||
uint32_t j;
|
||||
#endif
|
||||
|
||||
start:
|
||||
for(;;) {
|
||||
|
@ -348,6 +357,7 @@ start:
|
|||
if (state != BLK_READY)
|
||||
continue;
|
||||
|
||||
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
//raw read test
|
||||
if (1) {
|
||||
#define RAW_READ_SZ_MB 1
|
||||
|
@ -367,6 +377,7 @@ start:
|
|||
(RAW_READ_SZ_MB * 1024UL * 1000) / (et - st));
|
||||
chThdSetPriority(NORMALPRIO);
|
||||
}
|
||||
#endif
|
||||
|
||||
usbDbgPuts("FS: Block driver ready, try mount...");
|
||||
|
||||
|
@ -390,6 +401,7 @@ start:
|
|||
break;
|
||||
}
|
||||
|
||||
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
//FATFS test
|
||||
if (1) {
|
||||
UINT bw;
|
||||
|
@ -448,6 +460,7 @@ start:
|
|||
scan_files(chp, (char *)fbuff);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
usbDbgPuts("FS: Tests done, restarting in 3s");
|
||||
chThdSleepMilliseconds(3000);
|
||||
|
@ -527,6 +540,332 @@ static void ThreadTestHID(void *p) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if HAL_USBH_USE_UVC
|
||||
#include "usbh/dev/uvc.h"
|
||||
|
||||
static THD_WORKING_AREA(waTestUVC, 1024);
|
||||
|
||||
#if UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
static const uint8_t jpeg_header_plus_dht[] = {
|
||||
0xff, 0xd8, // SOI
|
||||
0xff, 0xe0, // APP0
|
||||
0x00, 0x10, // APP0 header size (including this field, but excluding preceding)
|
||||
0x4a, 0x46, 0x49, 0x46, 0x00, // ID string 'JFIF\0'
|
||||
0x01, 0x01, // version
|
||||
0x00, // bits per type
|
||||
0x00, 0x00, // X density
|
||||
0x00, 0x00, // Y density
|
||||
0x00, // X thumbnail size
|
||||
0x00, // Y thumbnail size
|
||||
0xFF, 0xC4, 0x01, 0xA2, 0x00,
|
||||
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 ,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
0x10,
|
||||
0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d,
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
|
||||
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
|
||||
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
||||
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
|
||||
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
|
||||
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa,
|
||||
0x11,
|
||||
0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77,
|
||||
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
|
||||
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
||||
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
|
||||
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
|
||||
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
|
||||
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
|
||||
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
|
||||
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
|
||||
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
|
||||
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
|
||||
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
|
||||
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
};
|
||||
#endif
|
||||
|
||||
static void ThreadTestUVC(void *p) {
|
||||
(void)p;
|
||||
USBHUVCDriver *const uvcdp = &USBHUVCD[0];
|
||||
|
||||
for(;;) {
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
//chSysLock();
|
||||
//state = usbhuvcGetDriverState(&USBHUVCD[0]);
|
||||
//chSysUnlock();
|
||||
if (usbhuvcGetState(&USBHUVCD[0]) != USBHUVC_STATE_ACTIVE)
|
||||
continue;
|
||||
|
||||
usbDbgPuts("UVC: Webcam connected");
|
||||
|
||||
/* ************************************ */
|
||||
/* Find best configuration */
|
||||
/* ************************************ */
|
||||
usbDbgPuts("UVC: Find best configuration");
|
||||
|
||||
generic_iterator_t ics;
|
||||
const usbh_uvc_format_mjpeg_t *format;
|
||||
|
||||
uint32_t max_frame_sz = 0;
|
||||
uint16_t min_ep_sz;
|
||||
uint8_t best_frame_interval_index;
|
||||
const usbh_uvc_frame_mjpeg_t *best_frame = NULL;
|
||||
|
||||
|
||||
//find format MJPEG
|
||||
if (usbhuvcFindVSDescriptor(uvcdp, &ics, UVC_VS_FORMAT_MJPEG, TRUE) != HAL_SUCCESS)
|
||||
goto failed;
|
||||
|
||||
format = (const usbh_uvc_format_mjpeg_t *)ics.curr;
|
||||
usbDbgPrintf("\tSelect bFormatIndex=%d", format->bFormatIndex);
|
||||
|
||||
//find the most suitable frame (largest one within the bandwidth requirements)
|
||||
if (usbhuvcFindVSDescriptor(uvcdp, &ics, UVC_VS_FRAME_MJPEG, TRUE) != HAL_SUCCESS)
|
||||
goto failed;
|
||||
|
||||
do {
|
||||
const usbh_uvc_frame_mjpeg_t *const frame = (usbh_uvc_frame_mjpeg_t *)ics.curr;
|
||||
uint32_t frame_sz = frame->wWidth * frame->wHeight;
|
||||
|
||||
usbDbgPrintf("\t\tbFrameIndex=%d", frame->bFrameIndex);
|
||||
usbDbgPrintf("\t\t\twWidth=%d, wHeight=%d", frame->wWidth, frame->wHeight);
|
||||
usbDbgPrintf("\t\t\tdwMinBitRate=%u, dwMaxBitRate=%u", frame->dwMinBitRate, frame->dwMaxBitRate);
|
||||
usbDbgPrintf("\t\t\tdwMaxVideoFrameBufferSize=%u", frame->dwMaxVideoFrameBufferSize);
|
||||
usbDbgPrintf("\t\t\tdwDefaultFrameInterval=%u", frame->dwDefaultFrameInterval);
|
||||
|
||||
uint8_t j;
|
||||
for (j = 0; j < frame->bFrameIntervalType; j++) {
|
||||
uint32_t ep_sz =
|
||||
usbhuvcEstimateRequiredEPSize(uvcdp, (const uint8_t *)format, (const uint8_t *)frame, frame->dwFrameInterval[j]);
|
||||
|
||||
usbDbgPrintf("\t\t\tdwFrameInterval=%u, estimated EP size=%u", frame->dwFrameInterval[j], ep_sz);
|
||||
|
||||
if (ep_sz > 310)
|
||||
continue;
|
||||
|
||||
/* candidate found */
|
||||
|
||||
if (frame_sz >= max_frame_sz) {
|
||||
/* new best frame size */
|
||||
min_ep_sz = 0xffff;
|
||||
max_frame_sz = frame_sz;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ep_sz < min_ep_sz) {
|
||||
/* new best bitrate */
|
||||
min_ep_sz = ep_sz;
|
||||
usbDbgPuts("\t\t\tNew best candidate found");
|
||||
best_frame_interval_index = j;
|
||||
best_frame = frame;
|
||||
}
|
||||
}
|
||||
} while (usbhuvcFindVSDescriptor(uvcdp, &ics, UVC_VS_FRAME_MJPEG, FALSE) == HAL_SUCCESS);
|
||||
|
||||
failed:
|
||||
if (best_frame == NULL) {
|
||||
usbDbgPuts("\t\t\tCouldn't find suitable format/frame");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ************************************ */
|
||||
/* NEGOTIATION */
|
||||
/* ************************************ */
|
||||
usbDbgPuts("UVC: Start negotiation");
|
||||
|
||||
usbhuvcResetPC(uvcdp);
|
||||
usbh_uvc_ctrl_vs_probecommit_data_t *const pc = usbhuvcGetPC(uvcdp);
|
||||
|
||||
pc->bmHint = 0x0001;
|
||||
pc->bFormatIndex = format->bFormatIndex;
|
||||
pc->bFrameIndex = best_frame->bFrameIndex;
|
||||
pc->dwFrameInterval = best_frame->dwFrameInterval[best_frame_interval_index];
|
||||
|
||||
usbDbgPrintf("\tFirst probe, selecting bFormatIndex=%d, bFrameIndex=%d, dwFrameInterval=%u",
|
||||
pc->bFormatIndex, pc->bFrameIndex, pc->dwFrameInterval);
|
||||
|
||||
usbDbgPuts("SET_CUR (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc);
|
||||
if (usbhuvcProbe(uvcdp) != HAL_SUCCESS) {
|
||||
usbDbgPuts("\tFirst probe failed");
|
||||
continue;
|
||||
}
|
||||
usbDbgPuts("GET_CUR (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc);
|
||||
usbDbgPuts("GET_MIN (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc_min);
|
||||
usbDbgPuts("GET_MAX (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc_max);
|
||||
|
||||
pc->bmHint = 0x0001;
|
||||
pc->wCompQuality = uvcdp->pc_min.wCompQuality;
|
||||
|
||||
usbDbgPuts("SET_CUR (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc);
|
||||
usbDbgPrintf("\tSecond probe, selecting wCompQuality=%d", pc->wCompQuality);
|
||||
if (usbhuvcProbe(uvcdp) != HAL_SUCCESS) {
|
||||
usbDbgPuts("\tSecond probe failed");
|
||||
continue;
|
||||
}
|
||||
usbDbgPuts("GET_CUR (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc);
|
||||
usbDbgPuts("GET_MIN (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc_min);
|
||||
usbDbgPuts("GET_MAX (PROBE):"); usbhuvcPrintProbeCommit(&uvcdp->pc_max);
|
||||
|
||||
/* ************************************ */
|
||||
/* Commit negotiated parameters */
|
||||
/* ************************************ */
|
||||
usbDbgPuts("UVC: Commit negotiated parameters");
|
||||
usbDbgPuts("SET_CUR (COMMIT):"); usbhuvcPrintProbeCommit(&uvcdp->pc);
|
||||
if (usbhuvcCommit(uvcdp) != HAL_SUCCESS) {
|
||||
usbDbgPuts("\tCommit failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
usbDbgPuts("UVC: Ready to start streaming");
|
||||
|
||||
uint32_t npackets = 0;
|
||||
uint32_t payload = 0;
|
||||
uint32_t total = 0;
|
||||
uint32_t frame = 0;
|
||||
systime_t last = 0;
|
||||
usbhuvcStreamStart(uvcdp, 310);
|
||||
|
||||
uint8_t state = 0;
|
||||
static FIL fp;
|
||||
|
||||
for (;;) {
|
||||
msg_t msg, ret;
|
||||
ret = usbhuvcLockAndFetch(uvcdp, &msg, TIME_INFINITE);
|
||||
if (ret == MSG_RESET) {
|
||||
usbDbgPuts("UVC: Driver is unloading");
|
||||
break;
|
||||
} else if (ret == MSG_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((usbhuvc_message_base_t *)msg)->type == USBHUVC_MESSAGETYPE_DATA) {
|
||||
usbhuvc_message_data_t *const data = (usbhuvc_message_data_t *)msg;
|
||||
|
||||
if (data->length < data->data[0]) {
|
||||
usbDbgPrintf("UVC: Length error!");
|
||||
goto free_data;
|
||||
}
|
||||
|
||||
uint32_t message_payload = data->length - data->data[0];
|
||||
|
||||
total += data->length;
|
||||
payload += message_payload;
|
||||
npackets++;
|
||||
|
||||
#if UVC_TO_MSD_PHOTOS_CAPTURE
|
||||
char fn[20];
|
||||
UINT bw;
|
||||
bool with_dht = true;
|
||||
uint8_t *message_data = data->data + data->data[0];
|
||||
if (frame & 7) goto check_eof;
|
||||
|
||||
if (state == 1) {
|
||||
if (message_payload < 12) goto check_eof;
|
||||
if (strncmp("AVI1", (const char *)message_data + 6, 4)) {
|
||||
with_dht = false;
|
||||
} else {
|
||||
uint16_t skip = (message_data[4] << 8) + message_data[5] + 4;
|
||||
if (skip > message_payload) goto check_eof;
|
||||
message_data += skip;
|
||||
message_payload -= skip;
|
||||
}
|
||||
|
||||
chsnprintf(fn, sizeof(fn), "/img%d.jpg", frame);
|
||||
if (f_open(&fp, fn, FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
|
||||
if (with_dht && f_write(&fp, jpeg_header_plus_dht, sizeof(jpeg_header_plus_dht), &bw) != FR_OK) {
|
||||
usbDbgPuts("UVC->MSD: File write error");
|
||||
f_close(&fp);
|
||||
state = 0;
|
||||
}
|
||||
state = 2;
|
||||
} else {
|
||||
usbDbgPuts("UVC->MSD: File open error");
|
||||
state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == 2) {
|
||||
if (f_write(&fp, message_data, message_payload, &bw) != FR_OK) {
|
||||
usbDbgPuts("UVC->MSD: File write error");
|
||||
f_close(&fp);
|
||||
state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
check_eof:
|
||||
#endif
|
||||
if (data->data[1] & UVC_HDR_EOF) {
|
||||
usbDbgPrintf("UVC: FRAME #%d, delta=%03dticks, #packets=%d, useful_payload=%dbytes, total=%dbytes",
|
||||
frame, data->timestamp - last , npackets, payload, total);
|
||||
last = data->timestamp;
|
||||
npackets = 0;
|
||||
payload = 0;
|
||||
total = 0;
|
||||
frame++;
|
||||
if (state == 2) {
|
||||
f_close(&fp);
|
||||
}
|
||||
state = 1;
|
||||
}
|
||||
free_data:
|
||||
usbhuvcFreeDataMessage(uvcdp, data);
|
||||
} else {
|
||||
usbhuvc_message_status_t *const status = (usbhuvc_message_status_t *)msg;
|
||||
const uint8_t *const stat = status->data;
|
||||
switch (stat[0] & 0x0f) {
|
||||
case 1:
|
||||
usbDbgPrintf("UVC: STATUS Control event, "
|
||||
"bOriginator=%d, bEvent=%d, bSelector=%d, bAttribute=%d",
|
||||
stat[1], stat[2], stat[3], stat[4]);
|
||||
break;
|
||||
case 2:
|
||||
usbDbgPrintf("UVC: STATUS Streaming event, "
|
||||
"bOriginator=%d, bEvent=%d, bValue=%d",
|
||||
stat[1], stat[2], stat[3]);
|
||||
break;
|
||||
default:
|
||||
usbDbgPrintf("UVC: STATUS unknown status report = %d", stat[0]);
|
||||
break;
|
||||
}
|
||||
usbhuvcFreeStatusMessage(uvcdp, status);
|
||||
}
|
||||
usbhuvcUnlock(uvcdp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(void) {
|
||||
|
||||
|
@ -566,6 +905,10 @@ int main(void) {
|
|||
chThdCreateStatic(waTestHID, sizeof(waTestHID), NORMALPRIO, ThreadTestHID, 0);
|
||||
#endif
|
||||
|
||||
#if HAL_USBH_USE_UVC
|
||||
chThdCreateStatic(waTestUVC, sizeof(waTestUVC), NORMALPRIO + 1, ThreadTestUVC, 0);
|
||||
#endif
|
||||
|
||||
//turn on USB power
|
||||
palClearPad(GPIOC, GPIOC_OTG_FS_POWER_ON);
|
||||
|
||||
|
|
Loading…
Reference in New Issue