969 lines
32 KiB
C
969 lines
32 KiB
C
/*
|
|
ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio
|
|
|
|
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.
|
|
*/
|
|
|
|
#include "ch.h"
|
|
#include "hal.h"
|
|
#include "ff.h"
|
|
#include <string.h>
|
|
#include "usbh/debug.h" /* for _usbh_dbg/_usbh_dbgf */
|
|
|
|
|
|
#define UVC_TO_MSD_PHOTOS_CAPTURE FALSE
|
|
|
|
|
|
#if HAL_USBH_USE_FTDI || HAL_USBH_USE_AOA
|
|
static uint8_t buf[] =
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_FTDI
|
|
#include "usbh/dev/ftdi.h"
|
|
#include "shell.h"
|
|
#include "chprintf.h"
|
|
|
|
static THD_WORKING_AREA(waTestFTDI, 1024);
|
|
|
|
#define SHELL_WA_SIZE THD_WORKING_AREA_SIZE(2048)
|
|
#define TEST_WA_SIZE THD_WORKING_AREA_SIZE(256)
|
|
|
|
static void cmd_write(BaseSequentialStream *chp, int argc, char *argv[]) {
|
|
|
|
(void)argv;
|
|
if (argc > 0) {
|
|
chprintf(chp, "Usage: write\r\n");
|
|
return;
|
|
}
|
|
|
|
while (chnGetTimeout((BaseChannel *)chp, TIME_IMMEDIATE) != Q_TIMEOUT) {
|
|
//flush
|
|
}
|
|
|
|
while (chnGetTimeout((BaseChannel *)chp, TIME_IMMEDIATE) == Q_TIMEOUT) {
|
|
streamWrite(&FTDIPD[0], buf, sizeof buf - 1);
|
|
}
|
|
chprintf(chp, "\r\n\nstopped\r\n");
|
|
}
|
|
|
|
static const ShellCommand commands[] = {
|
|
{"write", cmd_write},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static const ShellConfig shell_cfg1 = {
|
|
(BaseSequentialStream *)&FTDIPD[0],
|
|
commands
|
|
};
|
|
|
|
static void ThreadTestFTDI(void *p) {
|
|
(void)p;
|
|
USBHFTDIPortDriver *const ftdipp = &FTDIPD[0];
|
|
USBHDriver *host = NULL;
|
|
(void)host;
|
|
|
|
chRegSetThreadName("FTDI");
|
|
|
|
shellInit();
|
|
|
|
start:
|
|
while (usbhftdipGetState(ftdipp) != USBHFTDIP_STATE_ACTIVE) {
|
|
chThdSleepMilliseconds(100);
|
|
}
|
|
|
|
host = usbhftdipGetHost(ftdipp);
|
|
_usbh_dbg(host, "FTDI: Connected");
|
|
|
|
USBHFTDIPortConfig config = {
|
|
115200,
|
|
USBHFTDI_FRAMING_DATABITS_8 | USBHFTDI_FRAMING_PARITY_NONE | USBHFTDI_FRAMING_STOP_BITS_1,
|
|
USBHFTDI_HANDSHAKE_NONE,
|
|
0,
|
|
0
|
|
};
|
|
|
|
usbhftdipStart(ftdipp, &config);
|
|
|
|
//loopback
|
|
if (0) {
|
|
for(;;) {
|
|
msg_t m = streamGet(ftdipp);
|
|
if (m < MSG_OK) {
|
|
_usbh_dbg(host, "FTDI: Disconnected");
|
|
goto start;
|
|
}
|
|
streamPut(ftdipp, (uint8_t)m);
|
|
if (m == 'q')
|
|
break;
|
|
}
|
|
}
|
|
|
|
//shell test
|
|
if (1) {
|
|
thread_t *shelltp = NULL;
|
|
for(;;) {
|
|
if (usbhftdipGetState(ftdipp) != USBHFTDIP_STATE_READY)
|
|
goto start;
|
|
if (!shelltp) {
|
|
shelltp = chThdCreateFromHeap(NULL, SHELL_WA_SIZE,
|
|
"shell", NORMALPRIO,
|
|
shellThread, (void *)&shell_cfg1);
|
|
} else if (chThdTerminatedX(shelltp)) {
|
|
chThdRelease(shelltp);
|
|
if (usbhftdipGetState(ftdipp) != USBHFTDIP_STATE_READY)
|
|
goto start;
|
|
break;
|
|
}
|
|
chThdSleepMilliseconds(100);
|
|
}
|
|
}
|
|
|
|
//FTDI uart RX to debug TX bridge
|
|
if (0) {
|
|
for(;;) {
|
|
msg_t m = streamGet(ftdipp);
|
|
if (m < MSG_OK) {
|
|
_usbh_dbg(host, "FTDI: Disconnected");
|
|
goto start;
|
|
}
|
|
sdPut(&SD2, (uint8_t)m);
|
|
if (m == 'q')
|
|
break;
|
|
}
|
|
}
|
|
|
|
//write speed test
|
|
if (1) {
|
|
usbhftdipStop(ftdipp);
|
|
config.speed = 3000000;
|
|
usbhftdipStart(ftdipp, &config);
|
|
|
|
systime_t st, et;
|
|
int i;
|
|
for (i = 0; i < 5; i++) {
|
|
uint32_t bytes = config.speed / 10;
|
|
uint32_t times = bytes / 1024;
|
|
st = chVTGetSystemTimeX();
|
|
while (times--) {
|
|
if (streamWrite(ftdipp, buf, 1024) < 1024) {
|
|
_usbh_dbg(host, "FTDI: Disconnected");
|
|
goto start;
|
|
}
|
|
bytes -= 1024;
|
|
}
|
|
if (bytes) {
|
|
if (streamWrite(ftdipp, buf, bytes) < bytes) {
|
|
_usbh_dbg(host, "FTDI: Disconnected");
|
|
goto start;
|
|
}
|
|
}
|
|
et = chVTGetSystemTimeX();
|
|
_usbh_dbgf(host, "\tRate=%uB/s", (config.speed * 100) / (et - st));
|
|
}
|
|
}
|
|
|
|
//single character write test (tests the timer)
|
|
if (0) {
|
|
for (;;) {
|
|
if (streamPut(ftdipp, 'A') != MSG_OK) {
|
|
_usbh_dbg(host, "FTDI: Disconnected");
|
|
goto start;
|
|
}
|
|
chThdSleepMilliseconds(100);
|
|
}
|
|
}
|
|
|
|
usbhftdipStop(ftdipp);
|
|
|
|
_usbh_dbg(host, "FTDI: Tests done, restarting in 3s");
|
|
chThdSleepMilliseconds(3000);
|
|
|
|
goto start;
|
|
}
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_AOA
|
|
#include "usbh/dev/aoa.h"
|
|
#include "chprintf.h"
|
|
|
|
static THD_WORKING_AREA(waTestAOA, 1024);
|
|
|
|
#define SHELL_WA_SIZE THD_WORKING_AREA_SIZE(2048)
|
|
#define TEST_WA_SIZE THD_WORKING_AREA_SIZE(256)
|
|
|
|
static void ThreadTestAOA(void *p) {
|
|
(void)p;
|
|
USBHAOADriver *const aoap = &USBHAOAD[0];
|
|
USBHAOAChannel *const aoacp = &aoap->channel;
|
|
USBHDriver *host = NULL;
|
|
(void)host;
|
|
|
|
chRegSetThreadName("AOA");
|
|
|
|
start:
|
|
while (usbhaoaGetState(aoap) != USBHAOA_STATE_READY) {
|
|
chThdSleepMilliseconds(100);
|
|
}
|
|
|
|
host = usbhaoaGetHost(aoap);
|
|
_usbh_dbg(host, "AOA: Connected");
|
|
|
|
if (usbhaoaGetChannelState(aoap) != USBHAOA_CHANNEL_STATE_READY) {
|
|
usbhaoaChannelStart(aoap);
|
|
_usbh_dbg(host, "AOA: Channel started");
|
|
}
|
|
|
|
//loopback
|
|
if (1) {
|
|
for(;;) {
|
|
msg_t m = streamGet(aoacp);
|
|
if (m < MSG_OK) {
|
|
_usbh_dbg(host, "AOA: Disconnected");
|
|
goto start;
|
|
}
|
|
streamPut(aoacp, (uint8_t)m);
|
|
if (m == 'q')
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define AOA_WRITE_SPEED_TEST_BYTES 3000000UL
|
|
//write speed test
|
|
if (1) {
|
|
systime_t st, et;
|
|
int i;
|
|
for (i = 0; i < 5; i++) {
|
|
uint32_t bytes = AOA_WRITE_SPEED_TEST_BYTES;
|
|
uint32_t times = bytes / 1024;
|
|
st = chVTGetSystemTimeX();
|
|
while (times--) {
|
|
if (streamWrite(aoacp, buf, 1024) < 1024) {
|
|
_usbh_dbg(host, "AOA: Disconnected");
|
|
goto start;
|
|
}
|
|
bytes -= 1024;
|
|
}
|
|
if (bytes) {
|
|
if (streamWrite(aoacp, buf, bytes) < bytes) {
|
|
_usbh_dbg(host, "AOA: Disconnected");
|
|
goto start;
|
|
}
|
|
}
|
|
et = chVTGetSystemTimeX();
|
|
_usbh_dbgf(host, "\tRate=%uB/s", AOA_WRITE_SPEED_TEST_BYTES / (et - st) * 100);
|
|
}
|
|
}
|
|
|
|
//single character write test (tests the timer)
|
|
if (0) {
|
|
for (;;) {
|
|
if (streamPut(aoacp, 'A') != MSG_OK) {
|
|
_usbh_dbg(host, "AOA: Disconnected");
|
|
goto start;
|
|
}
|
|
chThdSleepMilliseconds(100);
|
|
}
|
|
}
|
|
|
|
usbhaoaChannelStop(aoap);
|
|
|
|
_usbh_dbg(host, "AOA: Tests done, restarting in 3s");
|
|
chThdSleepMilliseconds(3000);
|
|
|
|
goto start;
|
|
}
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_MSD
|
|
#include "usbh/dev/msd.h"
|
|
#include "ff.h"
|
|
#include "fatfs_devices.h"
|
|
|
|
static FATFS MSDLUN0FS;
|
|
|
|
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
|
static uint8_t fbuff[10240];
|
|
static FIL file;
|
|
|
|
static FRESULT scan_files(USBHDriver *host, BaseSequentialStream *chp, char *path) {
|
|
FRESULT res;
|
|
DIR dir;
|
|
UINT i;
|
|
static FILINFO fno;
|
|
|
|
res = f_opendir(&dir, path);
|
|
if (res == FR_OK) {
|
|
for (;;) {
|
|
res = f_readdir(&dir, &fno);
|
|
if (res != FR_OK || fno.fname[0] == 0)
|
|
break;
|
|
if (fno.fattrib & AM_DIR) {
|
|
i = strlen(path);
|
|
path[i++] = '/';
|
|
strcpy(&path[i], fno.fname);
|
|
res = scan_files(host, chp, path);
|
|
if (res != FR_OK)
|
|
break;
|
|
path[--i] = 0;
|
|
} else {
|
|
_usbh_dbgf(host, "FS: %s/%s", path, fno.fname);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
static THD_WORKING_AREA(waTestMSD, 1300);
|
|
static void ThreadTestMSD(void *p) {
|
|
(void)p;
|
|
|
|
FATFS *fsp;
|
|
DWORD clusters;
|
|
FRESULT res;
|
|
USBHDriver *host = NULL;
|
|
|
|
chRegSetThreadName("MSD");
|
|
|
|
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
|
BaseSequentialStream * const chp = (BaseSequentialStream *)&SD2;
|
|
systime_t st, et;
|
|
uint32_t j;
|
|
#endif
|
|
|
|
start:
|
|
for(;;) {
|
|
chThdSleepMilliseconds(100);
|
|
|
|
if (blkGetDriverState(&MSBLKD[0]) == BLK_ACTIVE) {
|
|
host = usbhmsdLUNGetHost(&MSBLKD[0]);
|
|
_usbh_dbg(host, "BLK: Active, connect....");
|
|
usbhmsdLUNConnect(&MSBLKD[0]);
|
|
}
|
|
if (blkGetDriverState(&MSBLKD[0]) != BLK_READY) {
|
|
continue;
|
|
}
|
|
|
|
_usbh_dbg(host, "BLK: Ready.");
|
|
|
|
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
|
//raw read test
|
|
if (1) {
|
|
#define RAW_READ_SZ_MB 1
|
|
#define NBLOCKS (sizeof(fbuff) / 512)
|
|
#define NITERATIONS ((RAW_READ_SZ_MB * 1024UL * 1024UL) / sizeof(fbuff))
|
|
uint32_t start = 0;
|
|
chThdSetPriority(HIGHPRIO);
|
|
_usbh_dbgf(host, "BLK: Raw read test (%dMB, %dB blocks)", RAW_READ_SZ_MB, sizeof(fbuff));
|
|
st = chVTGetSystemTime();
|
|
for (j = 0; j < NITERATIONS; j++) {
|
|
if (blkRead(&MSBLKD[0], start, fbuff, NBLOCKS) != HAL_SUCCESS)
|
|
goto start;
|
|
start += NBLOCKS;
|
|
}
|
|
et = chVTGetSystemTime();
|
|
_usbh_dbgf(host, "BLK: Raw read in %d ms, %dkB/s",
|
|
et - st,
|
|
(RAW_READ_SZ_MB * 1024UL * 1000) / (et - st));
|
|
chThdSetPriority(NORMALPRIO);
|
|
}
|
|
#endif
|
|
|
|
_usbh_dbg(host, "FS: Block driver ready, try mount...");
|
|
|
|
res = f_mount(&MSDLUN0FS, FATFSDEV_MSD_DRIVE, 1);
|
|
if (res != FR_OK) {
|
|
_usbh_dbg(host, "FS: Can't mount. Check file system.");
|
|
continue;
|
|
}
|
|
_usbh_dbg(host, "FS: Mounted.");
|
|
|
|
res = f_getfree(FATFSDEV_MSD_DRIVE, &clusters, &fsp);
|
|
if (res != FR_OK) {
|
|
_usbh_dbg(host, "FS: f_getfree() failed");
|
|
continue;
|
|
}
|
|
|
|
_usbh_dbgf(host, "FS: %lu free clusters, %lu sectors per cluster, %lu bytes free",
|
|
clusters, (uint32_t)MSDLUN0FS.csize,
|
|
clusters * (uint32_t)MSDLUN0FS.csize * MSBLKD[0].info.blk_size);
|
|
|
|
break;
|
|
}
|
|
|
|
#if !UVC_TO_MSD_PHOTOS_CAPTURE
|
|
//FATFS test
|
|
if (1) {
|
|
UINT bw;
|
|
const uint8_t *src;
|
|
const uint8_t *const start = (uint8_t *)0x08000000;
|
|
const uint8_t *const top = (uint8_t *)0x08020000;
|
|
|
|
//write test
|
|
if (1) {
|
|
_usbh_dbg(host, "FS: Write test (create file /test.dat, 1MB)");
|
|
f_open(&file, FATFSDEV_MSD_DRIVE "/test.dat", FA_CREATE_ALWAYS | FA_WRITE);
|
|
src = start;
|
|
st = chVTGetSystemTime();
|
|
for (j = 0; j < 2048; j++) {
|
|
if (f_write(&file, src, 512, &bw) != FR_OK)
|
|
goto start;
|
|
src += bw;
|
|
if (src >= top)
|
|
src = start;
|
|
}
|
|
et = chVTGetSystemTime();
|
|
_usbh_dbgf(host, "FS: Written 1MB in %d ms, %dkB/s",
|
|
et - st,
|
|
(1024UL*1000) / (et - st));
|
|
f_close(&file);
|
|
}
|
|
|
|
//read test
|
|
if (1) {
|
|
_usbh_dbg(host, "FS: Read test (read file /test.dat, 1MB, compare)");
|
|
f_open(&file, FATFSDEV_MSD_DRIVE "/test.dat", FA_READ);
|
|
src = start;
|
|
st = chVTGetSystemTime();
|
|
for (j = 0; j < 2048; j++) {
|
|
if (f_read(&file, fbuff, 512, &bw) != FR_OK)
|
|
goto start;
|
|
if (memcmp(src, fbuff, bw)) {
|
|
_usbh_dbgf(host, "Compare error @%08x", (uint32_t)src);
|
|
goto start;
|
|
}
|
|
src += bw;
|
|
if (src >= top)
|
|
src = start;
|
|
}
|
|
et = chVTGetSystemTime();
|
|
_usbh_dbgf(host, "FS: Read 1MB in %d ms, %dkB/s",
|
|
et - st,
|
|
(1024UL*1000) / (et - st));
|
|
f_close(&file);
|
|
}
|
|
|
|
//scan files test
|
|
if (1) {
|
|
_usbh_dbg(host, "FS: Scan files test");
|
|
strcpy((char *)fbuff, FATFSDEV_MSD_DRIVE);
|
|
scan_files(host, chp, (char *)fbuff);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
_usbh_dbg(host, "FS: Tests done, restarting in 3s");
|
|
chThdSleepMilliseconds(3000);
|
|
|
|
goto start;
|
|
|
|
}
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_HID
|
|
#include "usbh/dev/hid.h"
|
|
#include "chprintf.h"
|
|
|
|
static THD_WORKING_AREA(waTestHID, 1024);
|
|
|
|
static void _hid_report_callback(USBHHIDDriver *hidp, uint16_t len) {
|
|
uint8_t *report = (uint8_t *)hidp->config->report_buffer;
|
|
|
|
if (hidp->type == USBHHID_DEVTYPE_BOOT_MOUSE) {
|
|
_usbh_dbgf(hidp->dev->host, "Mouse report: buttons=%02x, Dx=%d, Dy=%d",
|
|
report[0],
|
|
(int8_t)report[1],
|
|
(int8_t)report[2]);
|
|
} else if (hidp->type == USBHHID_DEVTYPE_BOOT_KEYBOARD) {
|
|
_usbh_dbgf(hidp->dev->host, "Keyboard report: modifier=%02x, keys=%02x %02x %02x %02x %02x %02x",
|
|
report[0],
|
|
report[2],
|
|
report[3],
|
|
report[4],
|
|
report[5],
|
|
report[6],
|
|
report[7]);
|
|
} else {
|
|
_usbh_dbgf(hidp->dev->host, "Generic report, %d bytes", len);
|
|
}
|
|
}
|
|
|
|
static USBH_DEFINE_BUFFER(uint8_t report[HAL_USBHHID_MAX_INSTANCES][8]);
|
|
static USBHHIDConfig hidcfg[HAL_USBHHID_MAX_INSTANCES];
|
|
|
|
static void ThreadTestHID(void *p) {
|
|
(void)p;
|
|
uint8_t i;
|
|
static uint8_t kbd_led_states[HAL_USBHHID_MAX_INSTANCES];
|
|
|
|
chRegSetThreadName("HID");
|
|
|
|
for (i = 0; i < HAL_USBHHID_MAX_INSTANCES; i++) {
|
|
hidcfg[i].cb_report = _hid_report_callback;
|
|
hidcfg[i].protocol = USBHHID_PROTOCOL_BOOT;
|
|
hidcfg[i].report_buffer = report[i];
|
|
hidcfg[i].report_len = 8;
|
|
}
|
|
|
|
for (;;) {
|
|
for (i = 0; i < HAL_USBHHID_MAX_INSTANCES; i++) {
|
|
USBHHIDDriver *const hidp = &USBHHIDD[i];
|
|
if (usbhhidGetState(hidp) == USBHHID_STATE_ACTIVE) {
|
|
_usbh_dbgf(hidp->dev->host, "HID: Connected, HID%d", i);
|
|
usbhhidStart(hidp, &hidcfg[i]);
|
|
if (usbhhidGetType(hidp) != USBHHID_DEVTYPE_GENERIC) {
|
|
usbhhidSetIdle(hidp, 0, 0);
|
|
}
|
|
kbd_led_states[i] = 1;
|
|
} else if (usbhhidGetState(hidp) == USBHHID_STATE_READY) {
|
|
if (usbhhidGetType(hidp) == USBHHID_DEVTYPE_BOOT_KEYBOARD) {
|
|
USBH_DEFINE_BUFFER(uint8_t val);
|
|
val = kbd_led_states[i] << 1;
|
|
if (val == 0x08) {
|
|
val = 1;
|
|
}
|
|
usbhhidSetReport(hidp, 0, USBHHID_REPORTTYPE_OUTPUT, &val, 1);
|
|
kbd_led_states[i] = val;
|
|
}
|
|
}
|
|
}
|
|
chThdSleepMilliseconds(200);
|
|
}
|
|
|
|
}
|
|
#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];
|
|
|
|
chRegSetThreadName("UVC");
|
|
|
|
for(;;) {
|
|
chThdSleepMilliseconds(100);
|
|
|
|
//chSysLock();
|
|
//state = usbhuvcGetDriverState(&USBHUVCD[0]);
|
|
//chSysUnlock();
|
|
if (usbhuvcGetState(&USBHUVCD[0]) != USBHUVC_STATE_ACTIVE)
|
|
continue;
|
|
|
|
_usbh_dbg(uvcdp->dev->host, "UVC: Webcam connected");
|
|
|
|
/* ************************************ */
|
|
/* Find best configuration */
|
|
/* ************************************ */
|
|
_usbh_dbg(uvcdp->dev->host, "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;
|
|
_usbh_dbgf(uvcdp->dev->host, "\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;
|
|
|
|
_usbh_dbgf(uvcdp->dev->host, "\t\tbFrameIndex=%d", frame->bFrameIndex);
|
|
_usbh_dbgf(uvcdp->dev->host, "\t\t\twWidth=%d, wHeight=%d", frame->wWidth, frame->wHeight);
|
|
_usbh_dbgf(uvcdp->dev->host, "\t\t\tdwMinBitRate=%u, dwMaxBitRate=%u", frame->dwMinBitRate, frame->dwMaxBitRate);
|
|
_usbh_dbgf(uvcdp->dev->host, "\t\t\tdwMaxVideoFrameBufferSize=%u", frame->dwMaxVideoFrameBufferSize);
|
|
_usbh_dbgf(uvcdp->dev->host, "\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]);
|
|
|
|
_usbh_dbgf(uvcdp->dev->host, "\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;
|
|
_usbh_dbg(uvcdp->dev->host, "\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) {
|
|
_usbh_dbg(uvcdp->dev->host, "\t\t\tCouldn't find suitable format/frame");
|
|
continue;
|
|
}
|
|
|
|
/* ************************************ */
|
|
/* NEGOTIATION */
|
|
/* ************************************ */
|
|
_usbh_dbg(uvcdp->dev->host, "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];
|
|
|
|
_usbh_dbgf(uvcdp->dev->host, "\tFirst probe, selecting bFormatIndex=%d, bFrameIndex=%d, dwFrameInterval=%u",
|
|
pc->bFormatIndex, pc->bFrameIndex, pc->dwFrameInterval);
|
|
|
|
_usbh_dbg(uvcdp->dev->host, "SET_CUR (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc);
|
|
if (usbhuvcProbe(uvcdp) != HAL_SUCCESS) {
|
|
_usbh_dbg(uvcdp->dev->host, "\tFirst probe failed");
|
|
continue;
|
|
}
|
|
_usbh_dbg(uvcdp->dev->host, "GET_CUR (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc);
|
|
_usbh_dbg(uvcdp->dev->host, "GET_MIN (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc_min);
|
|
_usbh_dbg(uvcdp->dev->host, "GET_MAX (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc_max);
|
|
|
|
pc->bmHint = 0x0001;
|
|
pc->wCompQuality = uvcdp->pc_min.wCompQuality;
|
|
|
|
_usbh_dbg(uvcdp->dev->host, "SET_CUR (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc);
|
|
_usbh_dbgf(uvcdp->dev->host, "\tSecond probe, selecting wCompQuality=%d", pc->wCompQuality);
|
|
if (usbhuvcProbe(uvcdp) != HAL_SUCCESS) {
|
|
_usbh_dbg(uvcdp->dev->host, "\tSecond probe failed");
|
|
continue;
|
|
}
|
|
_usbh_dbg(uvcdp->dev->host, "GET_CUR (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc);
|
|
_usbh_dbg(uvcdp->dev->host, "GET_MIN (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc_min);
|
|
_usbh_dbg(uvcdp->dev->host, "GET_MAX (PROBE):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc_max);
|
|
|
|
/* ************************************ */
|
|
/* Commit negotiated parameters */
|
|
/* ************************************ */
|
|
_usbh_dbg(uvcdp->dev->host, "UVC: Commit negotiated parameters");
|
|
_usbh_dbg(uvcdp->dev->host, "SET_CUR (COMMIT):"); usbhuvcPrintProbeCommit(uvcdp, &uvcdp->pc);
|
|
if (usbhuvcCommit(uvcdp) != HAL_SUCCESS) {
|
|
_usbh_dbg(uvcdp->dev->host, "\tCommit failed");
|
|
continue;
|
|
}
|
|
|
|
_usbh_dbg(uvcdp->dev->host, "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) {
|
|
_usbh_dbg(uvcdp->dev->host, "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]) {
|
|
_usbh_dbgf(uvcdp->dev->host, "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) {
|
|
_usbh_dbg(uvcdp->dev->host, "UVC->MSD: File write error");
|
|
f_close(&fp);
|
|
state = 0;
|
|
}
|
|
state = 2;
|
|
} else {
|
|
_usbh_dbg(uvcdp->dev->host, "UVC->MSD: File open error");
|
|
state = 0;
|
|
}
|
|
}
|
|
|
|
if (state == 2) {
|
|
if (f_write(&fp, message_data, message_payload, &bw) != FR_OK) {
|
|
_usbh_dbg(uvcdp->dev->host, "UVC->MSD: File write error");
|
|
f_close(&fp);
|
|
state = 0;
|
|
}
|
|
}
|
|
|
|
check_eof:
|
|
#endif
|
|
if (data->data[1] & UVC_HDR_EOF) {
|
|
_usbh_dbgf(uvcdp->dev->host, "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:
|
|
_usbh_dbgf(uvcdp->dev->host, "UVC: STATUS Control event, "
|
|
"bOriginator=%d, bEvent=%d, bSelector=%d, bAttribute=%d",
|
|
stat[1], stat[2], stat[3], stat[4]);
|
|
break;
|
|
case 2:
|
|
_usbh_dbgf(uvcdp->dev->host, "UVC: STATUS Streaming event, "
|
|
"bOriginator=%d, bEvent=%d, bValue=%d",
|
|
stat[1], stat[2], stat[3]);
|
|
break;
|
|
default:
|
|
_usbh_dbgf(uvcdp->dev->host, "UVC: STATUS unknown status report = %d", stat[0]);
|
|
break;
|
|
}
|
|
usbhuvcFreeStatusMessage(uvcdp, status);
|
|
}
|
|
usbhuvcUnlock(uvcdp);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
#if USBH_DEBUG_MULTI_HOST
|
|
void USBH_DEBUG_OUTPUT_CALLBACK(USBHDriver *host, const uint8_t *buff, size_t len) {
|
|
(void)host;
|
|
#else
|
|
void USBH_DEBUG_OUTPUT_CALLBACK(const uint8_t *buff, size_t len) {
|
|
#endif
|
|
sdWrite(&SD2, buff, len);
|
|
sdWrite(&SD2, (const uint8_t *)"\r\n", 2);
|
|
}
|
|
|
|
int main(void) {
|
|
|
|
IWDG->KR = 0x5555;
|
|
IWDG->PR = 7;
|
|
|
|
halInit();
|
|
chSysInit();
|
|
|
|
//PA2(TX) and PA3(RX) are routed to USART2
|
|
sdStart(&SD2, NULL);
|
|
palSetPadMode(GPIOA, 2, PAL_MODE_ALTERNATE(7));
|
|
palSetPadMode(GPIOA, 3, PAL_MODE_ALTERNATE(7));
|
|
|
|
#if STM32_USBH_USE_OTG1
|
|
//VBUS - configured in board.h
|
|
//USB_FS - configured in board.h
|
|
#endif
|
|
|
|
#if STM32_USBH_USE_OTG2
|
|
#error "TODO: Initialize USB_HS pads"
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_MSD
|
|
chThdCreateStatic(waTestMSD, sizeof(waTestMSD), NORMALPRIO, ThreadTestMSD, 0);
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_FTDI
|
|
chThdCreateStatic(waTestFTDI, sizeof(waTestFTDI), NORMALPRIO, ThreadTestFTDI, 0);
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_AOA
|
|
chThdCreateStatic(waTestAOA, sizeof(waTestAOA), NORMALPRIO, ThreadTestAOA, 0);
|
|
#endif
|
|
|
|
#if HAL_USBH_USE_HID
|
|
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);
|
|
chThdSleepMilliseconds(100);
|
|
|
|
//start
|
|
#if STM32_USBH_USE_OTG1
|
|
usbhStart(&USBHD1);
|
|
_usbh_dbgf(&USBHD1, "Started");
|
|
#endif
|
|
#if STM32_USBH_USE_OTG2
|
|
usbhStart(&USBHD2);
|
|
_usbh_dbgf(&USBHD2, "Started");
|
|
#endif
|
|
|
|
for(;;) {
|
|
#if STM32_USBH_USE_OTG1
|
|
usbhMainLoop(&USBHD1);
|
|
#endif
|
|
#if STM32_USBH_USE_OTG2
|
|
usbhMainLoop(&USBHD2);
|
|
#endif
|
|
chThdSleepMilliseconds(100);
|
|
|
|
IWDG->KR = 0xAAAA;
|
|
}
|
|
}
|