summaryrefslogtreecommitdiff
path: root/src/moticam.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/moticam.c')
-rw-r--r--src/moticam.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/moticam.c b/src/moticam.c
new file mode 100644
index 0000000..232fb77
--- /dev/null
+++ b/src/moticam.c
@@ -0,0 +1,482 @@
+/* Camicro - Microscope camera viewer.
+ *
+ * This files add support for direct connexion with USB Moticam 3+.
+ *
+ * Copyright (C) 2019 Nicolas Schodet
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Contact :
+ * Web: http://ni.fr.eu.org/
+ * Email: <nico at ni.fr.eu.org>
+ */
+#include "moticam.h"
+#include "utils.h"
+
+#define ID_VENDOR 0x232f
+#define ID_PRODUCT 0x0100
+#define ID_PRODUCT_USB3 0x0101
+
+struct moticam_device {
+ struct device device;
+ libusb_context *usb;
+ libusb_device_handle *handle;
+ double exposure_ms;
+ int width;
+ int height;
+ bool exposure_pending;
+ double gain;
+ bool gain_pending;
+ size_t rawbuffer_size;
+ struct image image[2];
+ struct image *current_image;
+ struct image *other_image;
+ struct libusb_transfer *usb_transfer;
+ bool usb_transfer_done;
+ int drop;
+};
+
+static char *
+moticam_usb_poll(struct libusb_device_descriptor *desc);
+
+static struct device *
+moticam_usb_open(libusb_context *usb, libusb_device *usb_device,
+ GError **error);
+
+static bool
+moticam_stop(struct device *device, GError **error);
+
+const struct device_driver moticam_device_driver = {
+ "Moticam USB cameras",
+ &moticam_usb_poll,
+ &moticam_usb_open,
+};
+
+static const struct device_info_resolution moticam_device_info_resolution[] = {
+ { 2048, 1536 },
+ { 1024, 768 },
+ { 512, 384 },
+};
+
+static const struct device_info moticam_device_info = {
+ .exposure_min_ms = 1,
+ .exposure_max_ms = 5000,
+ .exposure_default_ms = 100,
+ .exposure_step_ms = 1,
+ .gain_min = 0.33,
+ .gain_max = 42.66,
+ .gain_default = 1,
+ .gain_step = 1.0 / 24,
+ .resolutions = 3,
+ .resolution = moticam_device_info_resolution,
+};
+
+static bool
+moticam_control_vendor(struct moticam_device *mdev, uint16_t wValue,
+ const uint8_t *data, int data_cnt, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ int r = libusb_control_transfer(mdev->handle, LIBUSB_REQUEST_TYPE_VENDOR,
+ 240, wValue, 0, (uint8_t *) data, data_cnt, 0);
+ if (r < 0) {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "can not send vendor control: %s", libusb_strerror(r));
+ return false;
+ }
+ return true;
+}
+
+static bool
+moticam_control_vendor_w(struct moticam_device *mdev, uint16_t wValue,
+ const uint16_t data0, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ uint8_t data[] = { (uint8_t) (data0 >> 8), (uint8_t) data0 };
+ return moticam_control_vendor(mdev, wValue, data, sizeof(data), error);
+}
+
+static bool
+moticam_control_reset(struct moticam_device *mdev, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ if (!moticam_control_vendor_w(mdev, 0xba00, 0x0000, error))
+ return false;
+ if (!moticam_control_vendor_w(mdev, 0xba00, 0x0001, error))
+ return false;
+ return true;
+}
+
+static bool
+moticam_control_exposure(struct moticam_device *mdev, double exposure_ms,
+ GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ int exposure_w = exposure_ms * 12.82;
+ if (exposure_w < 0x000c)
+ exposure_w = 0x000c;
+ else if (exposure_w > 0xffff)
+ exposure_w = 0xffff;
+ return moticam_control_vendor_w(mdev, 0xba09, exposure_w, error);
+}
+
+static bool
+moticam_control_gain(struct moticam_device *mdev, double gain, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ double gmin, gmax;
+ int xmin, xmax;
+ bool up = false;
+ if (gain <= 1.34) {
+ gmin = 0.33;
+ gmax = 1.33;
+ xmin = 0x08;
+ xmax = 0x20;
+ } else if (gain <= 2.68) {
+ gmin = 1.42;
+ gmax = 2.67;
+ xmin = 0x51;
+ xmax = 0x60;
+ } else {
+ gmin = 3;
+ gmax = 42.67;
+ xmin = 0x1;
+ xmax = 0x78;
+ up = true;
+ }
+ int x = (gain - gmin) / (gmax - gmin) * (xmax - xmin) + xmin;
+ if (x < xmin)
+ x = xmin;
+ else if (x > xmax)
+ x = xmax;
+ if (up)
+ x = (x << 8) | 0x60;
+ if (!moticam_control_vendor_w(mdev, 0xba2d, x, error))
+ return false;
+ if (!moticam_control_vendor_w(mdev, 0xba2b, x, error))
+ return false;
+ if (!moticam_control_vendor_w(mdev, 0xba2e, x, error))
+ return false;
+ if (!moticam_control_vendor_w(mdev, 0xba2c, x, error))
+ return false;
+ return true;
+}
+
+static bool
+moticam_control_resolution(struct moticam_device *mdev, int width, int height,
+ GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ static const uint8_t control_init[] = {
+ 0x00, 0x14, 0x00, 0x20, 0x05, 0xff, 0x07, 0xff };
+ if (!moticam_control_vendor(mdev, 0xba01, control_init,
+ sizeof(control_init), error))
+ return false;
+ static const uint8_t control_512x384[] = { 0x00, 0x03, 0x00, 0x03 };
+ static const uint8_t control_1024x768[] = { 0x00, 0x11, 0x00, 0x11 };
+ static const uint8_t control_2048x1536[] = { 0x00, 0x00, 0x00, 0x00 };
+ bool ret;
+ switch (width)
+ {
+ case 512:
+ g_assert(height == 384);
+ ret = moticam_control_vendor(mdev, 0xba22, control_512x384,
+ sizeof(control_512x384), error);
+ break;
+ case 1024:
+ g_assert(height == 768);
+ ret = moticam_control_vendor(mdev, 0xba22, control_1024x768,
+ sizeof(control_1024x768), error);
+ break;
+ case 2048:
+ g_assert(height == 1536);
+ ret = moticam_control_vendor(mdev, 0xba22, control_2048x1536,
+ sizeof(control_2048x1536), error);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ return ret;
+}
+
+static const struct device_info *
+moticam_get_info(struct device *device)
+{
+ return &moticam_device_info;
+}
+
+static void
+moticam_set_exposure(struct device *device, double exposure_ms)
+{
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ mdev->exposure_ms = exposure_ms;
+ mdev->exposure_pending = true;
+}
+
+static void
+moticam_set_gain(struct device *device, double gain)
+{
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ mdev->gain = gain;
+ mdev->gain_pending = true;
+}
+
+static void
+moticam_set_resolution(struct device *device, int width, int height,
+ int stride)
+{
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ g_assert(!mdev->current_image);
+ /* Ignore stride, store parameters. */
+ mdev->width = width;
+ mdev->height = height;
+}
+
+static void
+moticam_transfer_cb(struct libusb_transfer *transfer)
+{
+ struct moticam_device *mdev = transfer->user_data;
+ mdev->usb_transfer_done = true;
+}
+
+static bool
+moticam_start(struct device *device, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ g_assert(!mdev->current_image);
+ /* Configure camera. */
+ if (!moticam_control_exposure(mdev, 30.0, error))
+ return false;
+ if (!moticam_control_gain(mdev, mdev->gain, error))
+ return false;
+ if (!moticam_control_resolution(mdev, mdev->width, mdev->height, error))
+ return false;
+ if (!moticam_control_exposure(mdev, mdev->exposure_ms, error))
+ return false;
+ mdev->exposure_pending = false;
+ mdev->gain_pending = false;
+ utils_delay_us(100000);
+ /* Prepare sizes and buffers, request an extra frame to read the zero
+ * length packet. */
+ int image_size = mdev->width * mdev->height;
+ int frame_size = 16384;
+ mdev->rawbuffer_size = (image_size + frame_size)
+ / frame_size * frame_size;
+ for (int i = 0; i < 2; i++) {
+ struct image *image = &mdev->image[i];
+ image->width = mdev->width;
+ image->height = mdev->height;
+ image->stride = mdev->width;
+ image->format = IMAGE_FORMAT_SGRBG8;
+ image->pixels = libusb_dev_mem_alloc(mdev->handle,
+ mdev->rawbuffer_size);
+ if (!image->pixels)
+ g_error("can not allocate raw buffer");
+ g_assert(image->refs == 0);
+ image->release = NULL;
+ image->release_user_data = NULL;
+ }
+ mdev->current_image = &mdev->image[0];
+ mdev->other_image = &mdev->image[1];
+ /* Drop first frame. */
+ mdev->drop = 1;
+ /* Prepare and submit transfer. */
+ mdev->usb_transfer = libusb_alloc_transfer(0);
+ libusb_fill_bulk_transfer(mdev->usb_transfer, mdev->handle, 0x83,
+ mdev->current_image->pixels, mdev->rawbuffer_size,
+ moticam_transfer_cb, mdev, 30000);
+ int r = libusb_submit_transfer(mdev->usb_transfer);
+ if (r) {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "can not submit transfer: %s", libusb_strerror(r));
+ /* Cancel start. */
+ moticam_stop(device, NULL);
+ return false;
+ }
+ return true;
+}
+
+static struct image *
+moticam_read(struct device *device, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ g_assert(mdev->current_image);
+ if (mdev->usb_transfer_done) {
+ if (mdev->usb_transfer->status == LIBUSB_TRANSFER_COMPLETED) {
+ const int image_size = mdev->width * mdev->height;
+ struct image *image = NULL;
+ bool error_set = false;
+ /* Image received? Return it, else drop. */
+ if (!mdev->drop
+ && mdev->usb_transfer->actual_length == image_size) {
+ image = mdev->current_image;
+ image->refs = 1;
+ mdev->current_image = mdev->other_image;
+ mdev->other_image = image;
+ }
+ if (mdev->drop)
+ mdev->drop--;
+ /* Pending updates? */
+ if (image && !error_set && mdev->exposure_pending) {
+ if (!moticam_control_exposure(mdev, mdev->exposure_ms, error))
+ error_set = true;
+ else
+ mdev->exposure_pending = false;
+ }
+ if (image && !error_set && mdev->gain_pending) {
+ if (!moticam_control_gain(mdev, mdev->gain, error))
+ error_set = true;
+ else
+ mdev->gain_pending = false;
+ }
+ /* Start a new transfer. */
+ if (!error_set) {
+ g_assert(mdev->current_image->refs == 0);
+ mdev->usb_transfer_done = false;
+ mdev->usb_transfer->buffer = mdev->current_image->pixels;
+ int r = libusb_submit_transfer(mdev->usb_transfer);
+ if (r) {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "can not submit transfer: %s", libusb_strerror(r));
+ error_set = true;
+ }
+ }
+ /* Return image or NULL. */
+ if (error_set && image) {
+ image_unref(image);
+ image = NULL;
+ }
+ return image;
+ } else {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "transfer stopped");
+ return NULL;
+ }
+ } else
+ return NULL;
+}
+
+static bool
+moticam_stop(struct device *device, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ g_assert(mdev->current_image);
+ bool ret = true;
+ /* Cancel transfer if running. */
+ if (!mdev->usb_transfer_done) {
+ int r = libusb_cancel_transfer(mdev->usb_transfer);
+ if (r && r != LIBUSB_ERROR_NOT_FOUND) {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "can not cancel transfer: %s", libusb_strerror(r));
+ ret = false;
+ }
+ while (!mdev->usb_transfer_done) {
+ int r = libusb_handle_events(mdev->usb);
+ if (r) {
+ g_error("unable to handle libusb events: %s",
+ libusb_strerror(r));
+ }
+ }
+ }
+ /* Release. */
+ libusb_free_transfer(mdev->usb_transfer);
+ mdev->usb_transfer = NULL;
+ for (int i = 0; i < 2; i++) {
+ libusb_dev_mem_free(mdev->handle, mdev->image[i].pixels,
+ mdev->rawbuffer_size);
+ mdev->image[i].pixels = NULL;
+ }
+ mdev->current_image = NULL;
+ mdev->other_image = NULL;
+ /* Stop camera. */
+ for (int i = 0; ret && i < 3; i++)
+ ret = moticam_control_exposure(mdev, 0.0, error);
+ return ret;
+}
+
+static bool
+moticam_close(struct device *device, GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, false);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ bool ret = true;
+ if (mdev->current_image)
+ ret = moticam_stop(device, error);
+ libusb_close(mdev->handle);
+ free(device);
+ return ret;
+}
+
+static char *
+moticam_usb_poll(struct libusb_device_descriptor *desc)
+{
+ if (desc->idVendor == ID_VENDOR
+ && (desc->idProduct == ID_PRODUCT
+ || desc->idProduct == ID_PRODUCT_USB3))
+ return g_strdup("Moticam 3+");
+ else
+ return NULL;
+}
+
+static struct device *
+moticam_usb_open(libusb_context *usb, libusb_device *usb_device,
+ GError **error)
+{
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+ /* Create context. */
+ struct moticam_device *mdev = g_new(struct moticam_device, 1);
+ mdev->device.get_info = &moticam_get_info;
+ mdev->device.set_exposure = &moticam_set_exposure;
+ mdev->device.set_gain = &moticam_set_gain;
+ mdev->device.set_resolution = &moticam_set_resolution;
+ mdev->device.start = &moticam_start;
+ mdev->device.read = &moticam_read;
+ mdev->device.stop = &moticam_stop;
+ mdev->device.close = &moticam_close;
+ mdev->usb = usb;
+ mdev->handle = NULL;
+ mdev->width = 0;
+ mdev->height = 0;
+ mdev->exposure_ms = moticam_device_info.exposure_default_ms;
+ mdev->exposure_pending = false;
+ mdev->gain = moticam_device_info.gain_default;
+ mdev->gain_pending = false;
+ mdev->rawbuffer_size = 0;
+ for (int i = 0; i < 2; i++) {
+ mdev->image[i].refs = 0;
+ }
+ mdev->current_image = NULL;
+ mdev->other_image = NULL;
+ mdev->usb_transfer = NULL;
+ mdev->usb_transfer_done = false;
+ mdev->drop = 1;
+ /* Open USB device. */
+ int r = libusb_open(usb_device, &mdev->handle);
+ if (r) {
+ g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB,
+ "can not open device: %s", libusb_strerror(r));
+ g_free(mdev);
+ return NULL;
+ }
+ /* Reset camera. */
+ if (!moticam_control_reset(mdev, error)) {
+ g_free(mdev);
+ return NULL;
+ }
+ /* Done. */
+ return &mdev->device;
+}