Поиск

Управление турелью HSFW от Edmund Optics


Появилось у нашей лаборатории желание создать фотометр для Zeiss-1000 с минимумом разработок железа/софта и т.п. Одной из частей фотометра будут две турели High Speed Filter Wheel. Как обычно, железо огороженное. В отличие от предшественника Intelligent Filter Wheel (у которого вполне нормальный последовательный интерфейс с описанным в документации протоколе), у этой железяки только мастдайнутый установщик и никакой документации о протоколах!

При подключении к компьютеру железяка создает устройство /dev/hidrawX. На основе примера работы с этими устройствами из ядра я попытался определить, как же им управлять. И, в принципе, основные вещи реализовал (установка в «дом», установка на заданную позицию), но без понятия, как реализовать сброс (в отличие от usbdevfs эмуляторов последовательных портов здесь простым ioctl’ом перезапустить соединение не вышло). А сброс очень важно реализовать, т.к. любая проблема в протоколе вызывает «глухоту» контроллера: он перестает реагировать на управляющие команды.

Кстати, в опытах выяснил интересную вещь: если в первую десятку регистров hidraw под ведром 3.12 писать ненулевые данные, ядро (случайным образом, кстати: можно десяток раз так сделать без последствий, а можно с первого раза попасть) уходит в глубокий kernel panic, перезагрузка после которого чревата десятиминутным fsck’ом (это еще хорошо, что у меня один винт и небольшой)!

Для ознакомления с возможностью управлять я использовал немного модифицированный пример из linux kernel:

/*
 * Hidraw Userspace Example
 *
 * Copyright (c) 2010 Alan Ott <alan@signal11.us>
 * Copyright (c) 2010 Signal 11 Software
 *
 * The code may be used by anyone for any purpose,
 * and can serve as a starting point for developing
 * applications using hidraw.
 */

/* Linux */
#include <linux/types.h>
#include <linux/input.h>
#include <linux/hidraw.h>

/*
 * Ugly hack to work around failing compilation on systems that don't
 * yet populate new version of hidraw.h to userspace.
 */
#ifndef HIDIOCSFEATURE
#warning Please have your distro update the userspace kernel headers
#define HIDIOCSFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
#define HIDIOCGFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
#endif

/* Unix */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <usb.h>
#include <linux/usbdevice_fs.h>
/* C */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

const char *bus_str(int bus);

void get_features_list(int fd){
	int i, f, res;
	char buf1[256];
	char buf[256];
	for(f = 0; f < 256; ++f){
	if(f == 1) continue;
	// Get Feature
		memset(buf, 0, 256);
		buf[0] = f; // Report Number
		res = ioctl(fd, HIDIOCGFEATURE(256), buf);
		if(res <= 0) continue;
		if(f && 0 == memcmp(buf, buf1, res)) continue;
		memcpy(buf1, buf, res);
		printf("HIDIOCGFEATURE %d returned: %dn", f, res);
		printf("Report data (not containing the report number):nt");
		for (i = 0; i < res; i++)
			printf("0x%02hhx ", buf[i]);
		puts("n");
	}
}

int main(int argc, char **argv)
{
	int fd;
	int i, res, desc_size = 0;
	char buf[256];
	struct hidraw_report_descriptor rpt_desc;
	struct hidraw_devinfo info;
	char *device = "/dev/hidraw0";

	if (argc > 1)
		device = argv[1];

	/* Open the Device with non-blocking reads. In real life,
	   don't use a hard coded path; use libudev instead. */
	fd = open(device, O_RDWR|O_NONBLOCK);

	if (fd < 0) {
		perror("Unable to open device");
		return 1;
	}

	memset(&rpt_desc, 0x0, sizeof(rpt_desc));
	memset(&info, 0x0, sizeof(info));
	memset(buf, 0x0, sizeof(buf));

	// Get Report Descriptor Size
	res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
	if (res < 0)
		perror("HIDIOCGRDESCSIZE");
	else
		printf("Report Descriptor Size: %dn", desc_size);

	// Get Report Descriptor
	rpt_desc.size = desc_size;
	res = ioctl(fd, HIDIOCGRDESC, &rpt_desc);
	if (res < 0) {
		perror("HIDIOCGRDESC");
	} else {
		printf("Report Descriptor:n");
		for (i = 0; i < rpt_desc.size; i++)
			printf("%02hhx ", rpt_desc.value[i]);
		puts("n");
	}

	// Get Raw Name
	res = ioctl(fd, HIDIOCGRAWNAME(256), buf);
	if (res < 0)
		perror("HIDIOCGRAWNAME");
	else
		printf("Raw Name: %sn", buf);

	// Get Physical Location
	res = ioctl(fd, HIDIOCGRAWPHYS(256), buf);
	if (res < 0)
		perror("HIDIOCGRAWPHYS");
	else
		printf("Raw Phys: %sn", buf);

	// Get Raw Info
	res = ioctl(fd, HIDIOCGRAWINFO, &info);
	if (res < 0) {
		perror("HIDIOCGRAWINFO");
	} else {
		printf("Raw Info:n");
		printf("tbustype: %d (%s)n",
			info.bustype, bus_str(info.bustype));
		printf("tvendor: 0x%04hxn", info.vendor);
		printf("tproduct: 0x%04hxn", info.product);
	}

//goto rd;
	printf("nnFEATURES BEFORE:n");
	get_features_list(fd);

	memset(buf, 0xaa, 16);
	memset(buf+16, 0xee, 16);
	// Set Feature
	buf[0] = 20; // Report Number
	buf[1] = 1;
//	buf[2] = 3;
//	buf[3] = 3;
	res = ioctl(fd, HIDIOCSFEATURE(2), buf);
	if (res < 0)
		perror("HIDIOCSFEATURE");
	else
		printf("ioctl HIDIOCSFEATURE returned: %dn", res);
	sleep(2);
	printf("nnFEATURES AFTER:n");
	get_features_list(fd);
/*
rd:
	buf[0] = 22; // Report Number
	buf[1] = 1;
	res = ioctl(fd, HIDIOCSFEATURE(2), buf);
	if (res < 0)
		perror("HIDIOCSFEATURE");
	else
		printf("ioctl HIDIOCSFEATURE returned: %dn", res);
	//Send a Report to the Device
	char *mode = "WSHOMErn";
	res = write(fd, mode, 8);
	if (res < 0) {
		printf("Error: %dn", errno);
		perror("write");
	} else {
		printf("write() wrote %d bytesn", res);
	}


	// Get a report from the device
	res = read(fd, buf, 16);
	if (res < 0) {
		perror("read");
	} else {
		printf("read() read %d bytes:nt", res);
		for (i = 0; i < res; i++)
			printf("%hhx ", buf[i]);
		puts("n");
	}
*/
	close(fd);
	return 0;
}

const char *
bus_str(int bus)
{
	switch (bus) {
	case BUS_USB:
		return "USB";
		break;
	case BUS_HIL:
		return "HIL";
		break;
	case BUS_BLUETOOTH:
		return "Bluetooth";
		break;
	case BUS_VIRTUAL:
		return "Virtual";
		break;
	default:
		return "Other";
		break;
	}
}


Видно, что активных регистров немного, а по их содержимому можно примерно предположить что есть что.
Я начал с того, что писал число 2 в каждый из этих регистров (вот на первых у меня и вылезали kernel panic’и). Обнаружилось, что запись числа 1-5 в регистр 20 вызывает перемещение турели в указанную позицию, а вот если туда записать что-то вне допустимого диапазона, контроллер «глохнет».
Вот пример выдачи при перемещении из позиции 1 в позицию 2:

FEATURES BEFORE:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0xff 0x00 0x00 0x01 0x00

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0x00 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0x00 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

ioctl HIDIOCSFEATURE returned: 4

FEATURES AFTER:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0xff 0x00 0x00 0x02 0x00

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0xff 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0x00 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00


Итак, десятый регистр — статусный. В его байте №4 содержится номер текущей позиции, байт №5 содержит код ошибки (при попытке выхода за диапазон это тройка), как его сбросить, я не знаю. Содержимое 11 регистра не меняется. Возможно, это идентификатор, который можно менять программно, нужно проверять (в мастдайке записать идентификатор и посмотреть, что будет). Регистры 0, 2 и 22 содержат информацию, которая меняется при записи данных (скажем, если в 21 регистр записать 32 байта данных, в этих регистрах будет совсем другое). Как я уже говорил, регистр 20 позволяет устанавливать колесо в требуемую позицию, а регистр 21 при записи в него чего угодно крутит колесо в позицию «дом» (в отличие от турелей IRBISа, здесь каждая позиция не кодируется, а просто отмечается одним магнитом; позиция «1» отмечена дополнительным магнитом).

Вот выдача при попытке перемещения в позицию 255:

FEATURES BEFORE:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0xff 0x00 0x00 0x05 0x00

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0x00 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0x00 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

ioctl HIDIOCSFEATURE returned: 2

FEATURES AFTER:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0x00 0x00 0x00 0x00 0x03

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0xff 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0x00 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00


Как видим, код ошибки дублируется еще и в байте №2 регистра 22.
Запись 16-ти 0xaa и 16-ти 0xee в 21 регистр:


FEATURES BEFORE:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0xff 0x00 0x00 0x01 0x00

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0x00 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0x00 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

ioctl HIDIOCSFEATURE returned: 32

FEATURES AFTER:
HIDIOCGFEATURE 0 returned: 15
Report data (not containing the report number):
    0x00 0xaa 0xaa 0xaa 0xaa 0xaa 0xaa 0xaa 0xee 0xee 0xee 0xee 0xee 0xee 0xee

HIDIOCGFEATURE 2 returned: 14
Report data (not containing the report number):
    0xaa 0xaa 0xaa 0xaa 0xaa 0xaa 0xaa 0xee 0xee 0xee 0xee 0xee 0xee 0xee

HIDIOCGFEATURE 10 returned: 6
Report data (not containing the report number):
    0x0a 0xff 0x00 0x00 0x01 0x00

HIDIOCGFEATURE 11 returned: 7
Report data (not containing the report number):
    0x0b 0x01 0x00 0x00 0x05 0x41 0x00

HIDIOCGFEATURE 20 returned: 3
Report data (not containing the report number):
    0x14 0x00 0x00

HIDIOCGFEATURE 21 returned: 3
Report data (not containing the report number):
    0x15 0xff 0x00

HIDIOCGFEATURE 22 returned: 14
Report data (not containing the report number):
    0x16 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00


Видно также, что при изменении содержимого регистров 20 или 21 в ответе после этого изменения байты №1 соответствующего регистра и регистра 10 принимают значение 0xff.

На основе примера родилась такая простая управлялка:

/*
 * hsfw_rotate.c
 *
 * Copyright 2016 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include <linux/types.h>
#include <linux/input.h>
#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <usb.h>
#include <linux/usbdevice_fs.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#ifdef EBUG
void get_features_list(int fd){
	int i, f, res;
	char buf1[256];
	char buf[256];
	for(f = 0; f < 256; ++f){
	if(f == 1) continue;
	// Get Feature
		memset(buf, 0, 256);
		buf[0] = f; // Report Number
		res = ioctl(fd, HIDIOCGFEATURE(256), buf);
		if(res <= 0) continue;
		if(f && 0 == memcmp(buf, buf1, res)) continue;
		memcpy(buf1, buf, res);
		printf("HIDIOCGFEATURE %d returned: %dn", f, res);
		printf("Report data (not containing the report number):nt");
		for (i = 0; i < res; i++)
			printf("0x%02hhx ", buf[i]);
		puts("n");
	}
}
#endif

int main(int argc, char **argv){
	int fd;
	int i, res, pos = 0, getpos = 0;
	char buf[256] = {0};

	struct hidraw_devinfo info;
	char *device = "/dev/hidraw0";

	int getcurpos(){
		buf[0] = 10;
		res = ioctl(fd, HIDIOCGFEATURE(256), buf);
		if(res <= 0){
			perror("HIDIOCGFEATURE");
			return 0;
		}
		if(buf[0] != 10 || res != 6){
			fprintf(stderr, "Wrong answer format!");
			return 0;
		}
		if(buf[5]){
			fprintf(stderr, "Error in controller, code %dn", buf[5]);
			return -buf[4];
		}
		return buf[4];
	}
	if(argc > 3 || (argc == 2 && strcmp(argv[1], "-h") == 0)){
		printf("usage: %s [/dev/hidrawX] [position] (position==0 - home)n", argv[0]);
		return 1;
	}
	if(argc == 1) getpos = 1;
	else
	for(i = 1; i < argc; ++i){
		if(argv[i][0] == '/')
			device = argv[i];
		else{
			pos = atoi(argv[i]);
		}
	}
	if(pos < 0 || pos > 5){
		printf("Wrong position, set to HOMEn");
		pos = 0;
	}
	fd = open(device, O_RDWR|O_NONBLOCK);
	if(fd < 0){
		perror("Unable to open device");
		return 1;
	}
	memset(&info, 0x0, sizeof(info));
	//memset(buf, 0x0, sizeof(buf));

	res = ioctl(fd, HIDIOCGRAWINFO, &info);
	if(res < 0){
		perror("HIDIOCGRAWINFO");
		return 1;
	}else{
		if((info.vendor&0xffff) != 0x10c4 || (info.product&0xffff) != 0x82cd){
			fprintf(stderr, "Not Edmund Optics HSFW device!n");
			return 1;
		}
	}

	if(getpos){
		if((i = getcurpos()) == 0) return 1;
		printf("Current position: %dn", i);
		return 0;
	}
#ifdef EBUG
	printf("nnFEATURES BEFORE:n");
	get_features_list(fd);
#endif

	//memset(buf, 0, sizeof(buf));

	if(pos == 0){
		buf[0] = 21;
		pos = 1;
	}else{
		if(getcurpos() == pos){
			printf("Already at positionn");
			return 0;
		}
		buf[0] = 20;
		buf[1] = pos;
	}
	res = ioctl(fd, HIDIOCSFEATURE(2), buf);
	if (res < 0)
		perror("HIDIOCSFEATURE");
	else
		printf("ioctl HIDIOCSFEATURE returned: %dn", res);
	for(i = 0; i < 30; ++i){
		int cur = getcurpos();
		if(cur < 1){
			fprintf(stderr, "some error occured!n");
			return 1;
		}
		if(cur != pos) usleep(100000);
		else break;
		printf("."); fflush(stdout);
	}
	printf("nPosition %d %sreachedn", pos, (i != 30) ? "" : "not ");
#ifdef EBUG
	printf("nnFEATURES AFTER:n");
	get_features_list(fd);
#endif
	close(fd);
	return 0;
}


Если ее запускать без параметров, она покажет текущую позицию. В параметрах можно указать имя файла, с которым нужно работать (в принципе, можно открывать его через интерфейс udev по VID/PID, а какое именно колесо нужно — уточнять по его коду), и/или номер позиции, в которую следует установить турель (0 — «дом», даже если счетчик позиций говорит, что мы уже там).

Вот таким жутким велосипедостроением приходится порой заниматься, потому как разработчики железяки закрысили описать протокол (заодно передаю привет Canon’овцам)! То ли еще будет с ПЗС…

eddy-em.livejournal.com

Добавить комментарий