Работа с устройствами USB в Android / Хабр

1.1 Драйверы, работающие на функциональном уровне

Что такое USB-устройство? Это набор конечных точек. Но прикладному программисту, если честно, эти точки не интересны. Давным-давно, ещё в прошлом тысячелетии, когда последовательные порты делались на микросхеме UART16550, под неё был сделан функциональный драйвер для Windows, и все прикладные программисты привыкли работать с абстракциями именно этого драйвера.

И с этой привычкой трудно спорить. Представим на минутку, что с переходником USB-COM придётся работать в USB-шном стиле, на уровне конечных точек. Есть идеология CDC: две конечных точки туда-обратно, одна точка статуса в режиме прерываний и определённый набор команд, подаваемых через конечную точку EP0. Это всё описано в стандартах, относящихся к USB.

https://www.youtube.com/watch?v=5jaF3r_T4H8

Всё? Нет, некоторым этого мало! Prolific сделала свой набор команд для точки EP0, не совместимый с CDC. А FTDI – свой. Он не совместим ни с CDC, ни с Prolific. Так что, если бы прикладной программист работал бы с переходниками USB-COM на уровне конечных точек, ему пришлось бы нелегко.

К счастью, Prolific и FTDI предоставляют функциональные драйверы для своих чипов, а для CDC функциональный драйвер разработала сама Microsoft и прилагает его в составе Windows. Поэтому прикладной программист понятия не имеет, как работать с конкретным переходником (помню, мы целый NDA лет 15 назад с FTDI подписывали, чтобы получить от них руководство по их командам, причём я сразу же им послал информацию об ошибке в документе, так как пока работали бюрократы, я через дизассемблер всё сам уже успел изучить, так что сразу нашёл несовпадение их описания с моим).

То же касается и USB-накопителей. Мало кто знает, что физически там две конечных точки. Почти никому не интересно, как там надо гонять пакеты, чтобы посылать команды и данные. Как разруливать ошибки, знает ещё меньше людей. Все работают через драйвер usbstor.sys, предоставляемый Microsoft, который обеспечивает работу точно так же, как и с дисками, подключёнными через локальную шину материнской платы.Удобно? Однозначно!

Не обязательно. Если нас устраивает работа на прикладном уровне через конечные точки, то уже имеется целый ряд готовых драйверов, которые позволяют общаться с устройством через них. Некоторые из них мы сейчас и рассмотрим.

1.2 CyUSB

С этим драйвером я познакомился раньше других. Было это 12 лет назад. Вот с него и начну рассказ. Правда, скажу лишь вскользь. Фирма Cypress, выпуская замечательные контроллеры FX2LP, просто обязана была сделать драйвер, работающий на уровне конечных точек, чтобы облегчить жизнь своим клиентам.

И она его сделала. Кому интересно – ищите по слову CyAPI. Это DLL-ка, предоставляющая ряд функций для прикладного программиста. Я как системщик старался обойтись без DLL, поэтому сделал себе библиотеку, работающую с драйвером на уровне IOCTL запросов.

Главный недостаток данной библиотеки заключается в её лицензионном соглашении. Её можно использовать только с контроллерами Cypress. А чтобы всё было убедительнее, начиная с некоторых версий, драйвер стал просто зависать, если он работает с контроллерами других производителей.

1.3 WinUSB

Этот драйвер уходит своими корнями в инфраструктуру UMDF. Когда фирма Microsoft работала над возможностью запускать драйверы на пользовательском уровне защиты, они сделали специальный драйвер-прослойку WinUSB. Но через этот же драйвер можно работать и из обычных прикладных программ, а не только из UMDF-драйверов. Кому интересно – вбейте в поиск

WinUSB API

. Через эту функциональность можно делать то же самое, что и через CyUSB, но с контроллерами любых производителей.

1.4 Библиотека libusb

Вариант с WinUSB отличный, но не кроссплатформенный. Насколько мне известно, под Linux нет точно такого же API, который предоставляет Microsoft для Windows. Кроссплатформенный вариант – это использование библиотеки libusb. Причём под Windows эта библиотека по умолчанию опирается на всё тот же драйвер WinUSB.

Нашли драйвер, накатили его на устройство. Скачали библиотеку, начали через неё работать с этим драйвером. Надо будет – через неё же можно будет работать и под Linux. Замечательно? Да. Особенно если мы разработали полностью своё устройство. Но, увы, я просто обязан указать недостаток данного метода для общего случая.

Когда мы устанавливаем на устройство драйвер WinUSB, мы убираем оригинальный драйвер. В рамках данного блока статей мы теряем возможность общаться с нашим устройством при помощи утилиты ControlCenter. Ну, и я не упоминал в статьях про утилиту Streamer, позволяющую измерять скорость устройства… В общем, ею мы тоже не сможем пользоваться, если заменим штатный драйвер на WinUSB.

В рамках другой задачи мне надо было экспериментировать с аудиоустройствами из своего приложения. При этом я не хотел постигать все тонкости работы с ними. Я хотел только некоторые команды подавать, а чтобы всё остальное за меня делала сама операционка.

Можно ли работать из прикладной программы с устройствами, не пересаживая их на специальный драйвер? В принципе, да (правда, это очень аккуратное утверждение). Давайте я покажу этот метод… Правда, в конце — сильно раскритикую его.

1.5 Драйвер UsbDk

Библиотека libusb существует в двух версиях. Версия 0.1 и версия 1.0. Обе версии в настоящее время развиваются, создавая некоторую путаницу. И вот версия 1.0 может работать не только через драйвер WinUSB, но и через драйвер UsbDk. Последний является фильтр-драйвером. Что такое фильтр-драйверы? Вспоминаем детство, сказку о царе Салтане:

Собственно, фильтр-драйвер встаёт на пути IRP-пакетов от прикладной программы к основному драйверу и обратно. IRP — это гонцы, которые бегают туда-сюда. А фильтр-драйвер может их пропустить, не изменяя, а может подменить их содержимое. Он придуман для исправления ошибок, чтобы не менять основной драйвер, а просто немножко подправить пакеты, дабы те не вызывали проблем.

Очень схематично, опустив ряд неважных подробностей, это можно показать так:

Вот тут мы видим, что фильтр-драйвер UsbDK подсел на пути пакетов к нашему устройству (на самом деле, он подсел на пути ко всем USB-устройствам, так как прицепился к драйверу класса USB):

Если при открытии библиотеки libusb 1.0 сказать:

    libusb_init(&m_ctx);
    libusb_set_option(m_ctx, LIBUSB_OPTION_USE_USBDK);

то она будет использовать в качестве основы не WinUSB, а UsbDk. Ура? Не совсем. Приведу минимум две проблемы, создаваемые при использовании данного пути.

Первая проблема организационная. Если мы запустили программу, поработали, вышли, то всё будет хорошо. Но вот мы запустили программу, начали работать, а потом почему-то прервались. Да хоть у меня стояла точка останова, исполнение прервалось, я осмотрел переменные, увидел, что программа работает неверно, и завершил её.

Driver file operation error. DeviceIoControl failed (Cannot create a file when that file already exists.  Error code = 183)
Cannot Open USB Device

И всё. Выдёргивать-вставлять USB-кабель – бесполезно. Только полная перезагрузка Windows спасёт Отца Русской Демократии. Когда перезапускаешься третий-четвёртый раз за час – это начинает несколько раздражать. Поиск в сети не дал никаких результатов. Попытка бегло осмотреть исходники UsbDk – тоже, а на детальные разбирательства нет времени. Может, кто в комментариях чего подскажет…

Но на самом деле, эта проблема раздражает, но не является фатальной. Вторая проблема более серьёзная. Вот я поставил VirtualBox. Я просто запустил виртуальную машину и хочу подключить к ней, скажем, бластер. И что получаю?

Аналогично – любое другое устройство.

Поиск по сети даёт много вариантов типа: «У меня всё заработало после того, как я потёр заячьей лапкой по бубну из кожи тушканчика, спрыснутому кровью семидесятидвухлетней девственницы, полученной…» … Что там дальше в рецепте — сейчас уже не помню… Тем более, мне он не помог… Более осмысленные рекомендации требуют сносить фильтр-драйверы USB, пока не полегчает.

Проблема уходит, когда сносишь именно UsbDK. Ну, значится, так тому и быть. Хотя для экспериментов с аудио, других приемлемых вариантов я не нашёл. Так что драйвер я снёс, но дистрибутив – оставил. Пригодится. Именно поэтому описываю эту методику. Ну, и вдруг кто в комментариях подскажет, как обходить эти две проблемы. Тогда станет совсем здорово.

1.1.6 Итого

Итого, сегодня мы будем работать через библиотеку libusb, посадив устройство на драйвер WinUSB. Да, мы потеряем возможность работать с устройством через стандартные приложения от Cypress. Зато всё будет стабильно и хорошо.

2.1 Драйвер

Я уже много раз писал, что не очень сильно разбираюсь в уходе за пингвинами, поэтому просто перескажу слова своего начальника, опрошенного специально для написания данной статьи.

Некоторые устройства уже поддержаны в Линуксе по классу, либо по VID/PID. Вот, скажем, я подключил макетную плату ICE40 с ПЛИС Latice и подал сначала команду lsusb, чтобы увидеть USB устройства, а затем – уже полюбившуюся нам по прошлым статьям команду: ls –l /dev/serial/by-path, чтобы увидеть, что мост фирмы FTDI сам прикинулся двумя последовательными портами.

С такими устройствами, если ничего не предпринимать, можно работать только на функциональном уровне. Однако, если функциональный драйвер не подключился, как уверяет начальник, в отличие от Windows такие устройства не станут неизвестными (а потому недоступными).

Правда, есть чисто организационная подготовка, которую мы вынесем в собственный раздел.

2.2 Отключение требований прав администратора при работе с USB-устройствами

Главная особенность USB-устройств в Linux состоит в том, что по умолчанию для доступа к ним надо обладать правами администратора. То есть, для сборки «прошивки» для ПЛИС ICE40 (к теме статьи они не относятся, но по проекту я сейчас их осваиваю, причём под Linux, так что скриншоты готовить проще для них) мне достаточно набрать

make

, а вот если для «прошивки» я наберу

make prog

, то получу такое сообщение:

Не хватает прав. Надо набирать sudo make prog.

Чтобы понизить требуемые права, надо прописать правила. А чтобы быстро выяснить, как их прописать, я обычно даю такой запрос Гуглю:

Usb blaster Linux

Ссылок будет много, все они разной степени шаманства. Вот более-менее подробная ссылка: Using USB Blaster / USB Blaster II under Linux | Documentation | RocketBoards.org

1 Добавляем libusb в проект


Я скачал библиотеку libusb и распаковал её в каталог своего проекта. Чтобы подключить её к проекту, в файл *.pro пришлось добавить блок:

win32:{
LIBS  =  D:/Work/AS/2020/teensy/Qt/UsbSpeedTest/LibUSB/MinGW64/static/libusb-1.0.a
}

Кто сказал, что абсолютные пути – зло? Золотые слова! Я тоже так считаю. Но я уйму времени убил на эксперименты с относительными путями в этом месте. Ничего не получалось. Поэтому пока сделал так. Если кто-то знает тайну, как в проект добавляются относительные пути, да ещё так, чтобы работали, буду премного благодарен за разъяснения.

2 Класс для доступа к библиотеке

Обычно примеры для статей я пишу в слитном стиле. Но сегодняшний код получился несколько запутанным, поэтому пришлось вынести работу с платой FX3 в примитивный класс. Вот его интерфейсная часть:

#ifndef CUSBTESTER_H
#define CUSBTESTER_H
#include "./LibUSB/libusb.h"


class CUsbTester
{
public:
    CUsbTester();
    ~CUsbTester();
    virtual bool ConnectToDevice();
    virtual void DisconnectFromDevice();
    libusb_device_handle* m_hUsb;
protected:
    libusb_context * m_ctx = NULL;
};

#endif // CUSBTESTER_H

По функциям мы ещё пройдёмся, а пока я отмечу то, что у класса есть переменная-член, хранящая указатель на контекст, который будет нужен для вызова некоторых функций тонкой настройки библиотеки, и манипулятор (в простонародии — хэндл) устройства, необходимый для обращения к устройству.

В конструкторе происходит инициализация и тонкая настройка библиотеки:

//#define CY_VID_PID

CUsbTester::CUsbTester()
{
    libusb_init(&m_ctx);
#ifdef CY_VID_PID
    libusb_set_option(m_ctx, LIBUSB_OPTION_USE_USBDK);
#endif
    libusb_set_option(m_ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
    m_hUsb = 0;
}

Как видим, возможна работа библиотеки через драйвер CyUSB и фильтр UsbDk, но сейчас она отключена. И мы включаем расширенную диагностику работы библиотеки. Все сообщения о проблемах будут сыпаться в окно отладочного вывода. Помните, я показывал сообщение об ошибке при работе UsbDk? Без этой опции никто бы ничего не узнал. Ошибка и ошибка.

Деструктор, соответственно, всё деинициализирует. Класс тестовый, заботиться о наличии дополнительных пользователей библиотекой нет смысла. Так что просто деинициализируем и всё.

CUsbTester::~CUsbTester()
{
    DisconnectFromDevice();
    libusb_exit(m_ctx);
}

Подключение к устройству интересно лишь тем, что может работать по разным парам VID/PID. Как я уже говорил, я могу собирать «прошивку» под штатные VID/PID от Cypress, если идёт работа через UsbDk, и под те, на которые у меня нашёлся inf-файл, устанавливающий драйвер WinUSB. В остальном, ничем не примечательные типовые вызовы функции библиотеки libusb:

bool CUsbTester::ConnectToDevice()
{
#ifdef CY_VID_PID
    m_hUsb = libusb_open_device_with_vid_pid(m_ctx,0x4b4,0xf1);
#else
    m_hUsb = libusb_open_device_with_vid_pid(m_ctx,0x1234,0x0005);
#endif
    if (m_hUsb == 0)
    {
        qDebug()<<"Cannot Open USB Device";
        return false;
    }
    int res = libusb_claim_interface(m_hUsb,0);
    if (res != 0)
    {
        qDebug()<<"Cannot claim interface - "<<libusb_error_name(res);
    }
    return true;
}

Ну, и отключаемся от устройства тоже типовым методом:

void CUsbTester::DisconnectFromDevice()
{
    if (m_hUsb != 0)
    {
        libusb_release_interface(m_hUsb,0);
        libusb_close(m_hUsb);
        m_hUsb = 0;
    }
}

Собственно, всё. Функцию чтения из устройства я буду вызывать напрямую. Это противоречит принципам проектирования классов, но для опытов сойдёт. Я просто заметил, что код инициализации сильно разросся и отделил его от основного кода, вынеся в класс. А чтение — оно в одну строку выполняется, его отделять было не нужно.

3 Тестовая программа

Программа состоит из функции

main()

и тестовой функции.

Тестовая функция делится на две части. Первая часть измеряет скорость передачи данных. Вторая — проверяет то, что данные из счётчика приходят с инкрементом. При обнаружении разрыва – сведения об этом выдаются в консоль отладочного вывода. Собственно, вот текст функции:

bool TestStep(CUsbTester& tester,uint16_t* pData, const int bytesCnt)
{
    bool bResult = true;
    qDebug() << "Testing...";

    QElapsedTimer timer;
    timer.start();

    int actualLength;
    int res = libusb_bulk_transfer(tester.m_hUsb,0x81,(uint8_t*)pData,bytesCnt,&actualLength,10000);
    if (res != 0)
    {
        qDebug() << libusb_error_name(res);
        return false;
    }

    quint64 after = timer.elapsed();

    qDebug() << "Read Result = " << res;
    qDebug() << "Actual Length = " << actualLength;
    qDebug() << "Time = " << after;

    double speed = bytesCnt/after;
    qDebug() << speed << "Bytes / Sec";

    uint16_t prevData = pData[0];
    for (int i=1;i<bytesCnt/2;i  )
    {
        if (pData[i] != ((prevData   1)&0xffff))
        {
            qDebug() << Qt::hex << i << " : " << prevData << ", " << pData[i];
        }
        prevData = pData[i];
    }
    return bResult;
}


В будущем я планирую вызывать эту функцию многократно, но пока — функция

main()

вызывает её один раз:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CUsbTester tester;
    if (tester.ConnectToDevice())
    {

        QByteArray ar1;
        ar1.resize(128*1024*1024);

        for (int i=0;i<1;i  )
        {
            TestStep (tester,(uint16_t*)ar1.constData(),ar1.size());
        }
        tester.DisconnectFromDevice();
    }


    return a.exec();
}

Собственно, и вся программа.

4.1 Скорость

При тестовом прогоне мы видим, что скорость близка к требуемой. Напомню, что источник гонит 16-битные данные на скорости 60 МГц, так что нам требуется поток 120 миллионов байт в секунду. Когда на временных диаграммах наблюдался огромный дефицит колбасы, скорость была всего 30 Мегабайт в секунду. Даже меньше, чем практически достижимая скорость на шине USB 2.0. Так что текущие результаты вполне приемлемые.

Правда, чтобы окончательно успокоиться, я решил немного поэкспериментировать со скоростью. Чаще всего она была равна 119 0XX XXX байт в секунду. Изредка – 119 4XX XXX. Ну, и совсем редко – промежуточные значения, похожие на указанные выше. Сначала я решил, что это могут быть ошибки округления.

    QElapsedTimer timer;
    int actualLength;

    timer.start();
    int res = libusb_bulk_transfer(tester.m_hUsb,0x81,(uint8_t*)pData,bytesCnt,&actualLength,10000);
    quint64 after = timer.nsecsElapsed();

    if (res != 0)
    {
        qDebug() << libusb_error_name(res);
        return false;
    }

    qDebug() << "Read Result = " << res;
    qDebug() << "Actual Length = " << actualLength;
    qDebug() << "Time = " << after;

    quint64 size = bytesCnt;
    size *= 1000000000;
    quint64 speed = size/after;
    qDebug() << speed << "Bytes / Sec";

Результат ничуть не изменился. Следующее подозрение было, что источник имеет некоторую ошибку кварцевого резонатора и шлёт данные чуть медленнее, чем надо. Но другая плата даёт точно такие же показания! Значит, дело не в экземпляре. Мало того, осциллограф (правда, весьма дешёвый китайский) говорит, что тактовая частота нисколько не занижена, даже чуть завышена.

Тогда я полез в исходники библиотеки libusb и просто-таки утонул в коде, который исполняется при ожидании конца передачи данных. Там такая цепочка функций – просто закачаешься! И много, где может возникать задержка. Но давайте прикинем, какова задержка.

120 мегабайт в секунду… Округлим до ста. Это 1 мегабайт в 10 миллисекунд. А у нас просадка как раз на 1 мегабайт. Неужели начало и конец прокачки съедают так много времени? Как бы проверить хотя бы начерно? Я решил переписать на пробу код под прямой вызов WinUSB API.

Полностью объяснять я его не стану, там устройство открывается хитро, через SetupDi API. Я просто воспользовался готовыми своими старыми классами, поэтому всё было сделано под Visual Studio. Но вместо страшных километровых текстов ожидания завершения библиотеки WinUSB, моя функция чтения выглядит вполне канонически:

int CWinUSBConnector::ReadViaBulk (void* pData,int count,int timeOut)
{
	DWORD dwRead;

	ResetEvent (m_hEvRead);
	if ((!WinUsb_ReadPipe (m_hUsbDrive,m_nReadEP,(BYTE*)pData,count,&dwRead,&m_ovRead)) && (GetLastError() != ERROR_IO_PENDING))
	{
		return -1;
	}
	if (WaitForSingleObject(m_hEvRead,timeOut)!=WAIT_OBJECT_0)
	{
		WinUsb_AbortPipe (m_hUsbDrive,m_nReadEP);
		return -2;
	} else
	{
		WinUsb_GetOverlappedResult (m_hUsbDrive,&m_ovRead,&dwRead,FALSE);
	}
	return dwRead;
}

А тест теперь выглядит так:

#include <iostream>
#include <windows.h>

#include "./USB/WinUSBConnector.h"

CWinUSBConnector m_connector;


int main()
{
    std::cout << "Hello World!n";
	if (m_connector.Open() != 0)
	{
		std::cerr << "Cannot Open Driven";
		return 1;
	}

	LARGE_INTEGER Frequency;
	QueryPerformanceFrequency(&Frequency);

	static const UINT64 cnt = 128 * 1024 * 1024;
	BYTE* pData = new BYTE [cnt];

	LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;

	QueryPerformanceCounter(&StartingTime);
	UINT64 realSize = m_connector.ReadViaBulk(pData, (int)cnt, 10000);
	QueryPerformanceCounter(&EndingTime);
	if (realSize != cnt)
	{
		std::cerr << "Cannot Read Datan";
		return 2;
	}
	ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
	ElapsedMicroseconds.QuadPart *= 1000000;
	ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;

	UINT64 bytesPerSeconds = (cnt*1000000LL) / ElapsedMicroseconds.QuadPart;

	std::cout << "Test Finished! " << bytesPerSeconds << " Bytes  per second n";

	delete[] pData;
	return 0;
}

Теперь скорость стабильно находится в районе 119 2XX XXX байт в секунду. Вот какой красивый результат я подловил для красного словца:

А вот результат нескольких прогонов подряд:

В общем, никто не запрещает просадке возникать и на входе-выходе в функцию чтения. Если разрывов в показаниях счётчика не будет, то и ладно.

4.2 Пропуски в показаниях счётчика

Но на тему разрывов у нас тоже имеется пара строк в выводе основной тестовой программы:

Имеется два нарушения последовательности счёта. Неужели мы нарвались на выпадение данных? К счастью, всё в порядке. Множественные прогоны говорят, что проблемы всегда в одном и том же месте. И мы с таким уже сталкивались в одной из старых статей. У нас есть две точки буферизации данных: буфер контроллера и FIFO в ПЛИС.

Готовы ли мы принимать данные или нет, они с выхода счётчика будут заливаться в буфер FX3. Когда тот переполнится, заливка остановится. Кроме буфера FX3, есть ещё FIFO в ПЛИС. Вот у нас и имеется две ёмкости, которые заполнились, но счётчик продолжил работу. При чтении мы увидели разрывы в счёте.

Соответственно, мы наблюдаем явление переходных процессов. При реальной работе надо будет настроить источник так, чтобы он не заполнял буфер данными, пока мы не готовы их принимать (собственно, в статье про голову USB-Анализатора мы уже делали такую функцию через добавление бита «Go»), а пока – просто будем игнорировать ошибки в этой области. Считаем, что это – не баги, а фичи. Меняем проверку разрывов на такую:

Делаем массовый прогон для такого варианта… И тут я понял, кто даёт задержку… Отладочный вывод!!! Вот так всё выглядит:

Масса вспомогательных текстов, выдаваемых библиотекой. Ну и отлично. Закомментируем в конструкторе класса, обслуживающего libusb, строку:

// libusb_set_option(m_ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);

Получаем красоту, ничем не отличимую от того, что мы получали при прямом вызове WinUSB API (жаль только, что не идеальный результат):

Но главное – нет сообщений про разрывы в показаниях счётчика (известные точки разрывов мы игнорируем).

А что там нам про таймауты промежуточные говорили? Попробуем читать не 128, а 32 мегабайта! Получаем уже скорость вплоть до 119 8XX XXX мегабайт в секунду!

В общем, всё хорошо… Кроме одного. Я нашёл такой нестандартный вариант теста, при котором штатная «прошивка» FX3 подвисает. Но статья получилась такой большой, что как это найти, а главное – как исправить, я расскажу в следующий раз.

Пара слов про jtag

Если вы не знакомы с интерфейсом JTAG, прочтите

и снова возвращайтесь. Вообще говоря, JTAG Test Access Port — это, в первую очередь, интерфейс для отладки аппаратной части устройства, а не его программы, но, «чтобы два раза не бегать», прошивку и отладку кода через него поддерживают подавляющее большинство программируемых микросхем с этим интерфейсом.

С другой стороны, зачастую даже 4 свободных выводов (а для JTAG требуется не менее 4) на МК нет, да и большинство производителей страдает (а зачастую даже наслаждается) синдромом NIH, поэтому вместо JTAG встречаются разного рода проприентарные интерфейсы, такие как SWD от ARM (протокол тот же, только по 2 вывода вместо 4)

, BDM от Freescale и Motorola (протокол другой, достаточно одного двунаправленного вывода), Spy-Bi-Wire от Texas Instruments (еще один вариант JTAG с 2 выводами), DebugWIRE от AVR (упрощенный отладочный протокол, которому достаточно 1 вывода) и другие.

. На «голом» FT232H из вышеупомянутых вариантов отладочных интерфейсов поддерживается только JTAG, имейте это в виду.

Права доступа


Как и для прочих действий, Android требует, чтобы приложение получило разрешение на доступ к USB периферии. Существует 2 способа получить такое разрешение:

Поскольку для моих задач лишние вопросы к пользователю были нежелательны, я использовал первый способ.

Итак, нам необходимо добавить в манифест следующее:


А в res/xml/device_filter.xml вписать следующее:

Отмечу, что хотя общепринято указывать VID:PID в 16-ричной системе счисления, здесь они должны быть указаны в десятичной. В документации заявляется, что возможно указание только класса, без VID и PID, но у меня это не стало работать.

Принтеры

На примере принтера я покажу, как непосредственно использовать API android.hardware.usb. На уровне передачи данных все принтеры поддерживают стандартый класс USB устройств:

int UsbConstants.USB_CLASS_PRINTER = 7;


Класс предельно простой. В рамках этого класса устройство должно поддерживать:

int GET_DEVICE_ID = 0;
int GET_PORT_STATUS = 1;
int SOFT_RESET = 2;

Код, приведенный ниже, предоставляет функциональность, аналогичную устройству /dev/usb/lp в Linux. Далее нам нужен фильтр, преобразующий исходный документ в пакет данных, понятный конкретной модели принтера. Но это тема иной статьи. Как один из вариантов — можно собрать ghostscript с помощью NDK.

Для работы с устройством нам в первую очередь нужно:

1. Найти устройство. В примере для простоты я ищу первый попавшийся:

UsbDevice findDevice() {
    for (UsbDevice usbDevice: mUsbManager.getDeviceList().values()) {
        if (usbDevice.getDeviceClass() == UsbConstants.USB_CLASS_PRINTER) {
            return usbDevice;
        } else {
            UsbInterface usbInterface = findInterface(usbDevice);
            if (usbInterface != null) return usbDevice;
        }
    }
    return null;
}

UsbInterface findInterface(UsbDevice usbDevice) {
    for (int nIf = 0; nIf < usbDevice.getInterfaceCount(); nIf  ) {
        UsbInterface usbInterface = usbDevice.getInterface(nIf);
        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) {
            return usbInterface;
        }
    }
    return null;
}
UsbDevice mUsbDevice = findDevice();
UsbInterface mUsbInterface = findInterface(mUsbDevice);

2. Получить endpoint’ы:

for (int nEp = 0; nEp < mUsbInterface.getEndpointCount(); nEp  ) {
    UsbEndpoint tmpEndpoint = mUsbInterface.getEndpoint(nEp);
    if (tmpEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) continue;

    if ((mOutEndpoint == null)
            && (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_OUT)) {
        mOutEndpoint = tmpEndpoint;
    } else if ((mInEndpoint == null)
            && (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN)) {
        mInEndpoint = tmpEndpoint;
    }
}
if (mOutEndpoint == null) throw new IOException("No write endpoint: "   deviceName);

3. Непосредсвенно открыть устройство:

mConnection = mUsbManager.openDevice(mUsbDevice);
if (mConnection == null) throw new IOException("Can't open USB connection:"   deviceName);
mConnection.claimInterface (mUsbInterface, true);

4. После этого мы можем читать и писать в устройство:

public int read(final byte[] data) throws IOException {
    int size = Math.min(data.length, mInEndpoint.getMaxPacketSize());
    return mConnection.bulkTransfer(mInEndpoint, data, size, getReadTimeout());
}

public int write(final byte[] data, final int length) throws IOException {
    int offset = 0;

    while (offset < length) {
        int size = Math.min(length - offset, mInEndpoint.getMaxPacketSize());
        int bytesWritten = mConnection.bulkTransfer(mOutEndpoint,
            Arrays.copyOfRange(data, offset, offset   size), size, getWriteTimeout());

        if (bytesWritten <= 0) throw new IOException("None written");
        offset  = bytesWritten;
    }
    return offset;
}

5. По завершении работы — закрыть устройство:

mConnection.close();

Прошиваем и отлаживаем arm’ы

В силу того, что на FT232H основано много распространенных отладчиков, самым простым способом добавления поддержки нашей платы в IDE и другие утилиты является эмуляция этих самых отладчиков. Некоторые фичи, возможно, не будут работать, но и от написания своих драйверов этот трюк избавляет. Проще всего притвориться открытым отладчиком CoLink (без суффикса Ex),

отличается от большинства схем breakout-плат только наличием буфера. Более того, CooCox IDE напрямую поддерживает использование любых устройств на базе FT232H в качестве отладчика

, а для Keil uVision и IAR Workbench имеются плагины, причем ни Vendor ID, ни Product ID менять не понадобится, т.к. CoLink использует как стандартные для FT232H VID и PID, так и стандартные драйверы FTDI D2XX.

Если ваша ОС отличается от Windows и вас не интересуют ни Keil, ни IAR, ни CooCox IDE — обратите внимание на проекты

, которые имеют полную поддержку отладчиков на базе FT232H. Я работаю в Keil uVision4, и этот выбор был сделан не мной, поэтому подробнее об использовании FT232H в Linux и OS X рассказать пока не могу.


В Keil же достаточно установить

, выбрать правильный Target и CoLink в качестве отладчика в свойствах проекта, после чего как прошивка, так и отладка будет проходить из IDE без привлечения какого-либо стороннего софта.

К сожалению, я не могу найти полного списка совместимых с CoLink чипов (официальный есть по ссылке выше, но он далеко не полон), поэтому придется либо пробовать, либо пользоваться списком совместимости с OpenOCD. В общем, лучше всего просто попробовать, есть большая вероятность, что заработает сразу.

Прошиваем и отлаживаем xe166

Уверен, что даже многие «матерые волки» впервые слышат о данной 16-битной RISC-архитектуре, не очень популярной за пределами ее родины — Германии. Архитектура эта уже достаточно старая, ее начальная модификация была разработана в 1993 году концерном Siemens в сотрудничестве с ST для использования в промышленной автоматике, и называлась она тогда C166.

, широко применяются в немецкой автоматике и устаревать пока не собираются. Работу с ними преподают студентам технических ВУЗов соответствующих ИТ-специальностей, поэтому и мне пришлось столкнуться с этой архитектурой и чипами на ней.

Мое мнение — нормальные чипы для своих задач, очень много периферии (по 6 каналов CAN с поддержкой MultiCAN, к примеру), до 100 Мгц с производительностью 1 MIPS на Мгц, но не без недостатков — легко могут жрать по 120 мА на 100 Мгц без особой вычислительной нагрузки и практически полностью отключенной от тактования периферии, да и в программировании посложнее AVR, но на уровне с типичными ARM’ами того же сегмента. Ну и vendor lock-in, куда без него.

Вследствие редкости и малой распространенности на некорпоративном рынке отладчики для этой архитектуры либо невозможно купить совсем, либо на их стоимость можно приобрести сотню нормальных отладчиков для других более популярных архитектур. Но пристальное изучение

, на которой обнаруживается все тот же FT2232D, наводит на мысль, что отладчик можно добыть малой кровью. Так и есть, но в Infineon, в отличие от CooCox, решили все-таки поменять VID, PID и серийный номер установленного на девките FT2232D, поэтому это придется сделать и нам.

Подключаем девкит, ждем установки драйверов, запускаем FT_Prog, записываем VID, PID и серийный номер (я добавлю их в статью, когда доберусь до девкита, сейчас, каюсь, уже не помню), а затем прошиваем эти же значения (номер серийный можно слегка поменять)

в EEPROM нашей платы (вот тут то он и понадобился). Отключаем плату от ПК, подключаем заново — новые VID и PID подхватываются драйверами и далее работа с нашей платой мало чем отличается от работы с девкитом — все те же Keil и Infineon DAS в качестве отладчика.

Радиокот :: usb – rs-232 преобразователи

РадиоКот >Схемы >Цифровые устройства >Примочки к ПК >

USB – RS-232 преобразователи

Продолжаем разговор про шину USB и ее применение в радиолюбительской практике.

В прошлый раз (USB 1.1 хаб. Light – версия)
мы довольно успешно снабдили ПК десятком дополнительных USB-портов, теперь пришло время
начинать использовать все это изобилие.

Естественно, первое, что приходит в голову, применить эти порты для обмена данными с собственными
конструкциями на МК. Однако далеко не все МК сейчас имеют периферию для работы непосредственно
с USB, а программная реализация этого протокола достаточно сложна и отнимает немало процессорного
времени. С другой стороны, подавляющее большинство МК имеет «на борту» модуль универсального
асинхронного приемопередатчика, который может работать в режиме, совместимом с протоколом RS-232,
т.е. такой микроконтроллер при условии согласования уровней сигналов можно подключать
непосредственно к COM-порту компьютера. А при чем здесь USB, спросите вы? Отвечу: производители,
как всегда, подсуетились, в результате чего на рынке появилось несколько вариантов
преобразователей интерфейсов USB<->RS-232. Т.е. в нашем распоряжении есть устройство,
подключаемое к ПК по USB, а на выходе имеющее сигналы, понятные любому МК с модулем USART
(или даже с программно реализованным USART”ом). Здорово? Конечно, здорово, особенно с учетом
того, что ОС воспринимает такой переходник просто как еще один (виртуальный) СОМ-порт
(VCP: Virtual Com Port) и позволяет работать с ним с помощью обычных терминальных средств.

Вот о микросхемах, позволяющих все это реализовывать, у нас и пойдет сегодня речь…

Пожалуй, наиболее распространенными на сегодняшний день являются преобразователи FT232BM
от FTDI Ltd (USB 1.1) (ничего от Maxim не напоминает по названию? 😉 ), TUSB3410 от
Texas Instruments (USB 2.0), а так же PL-2303 от Prolific. Что за звери такие? Будем разбираться…

FT232BM выпускается в 32 выводном корпусе LQFP-32 с шагом выводов 0.8мм.

Микросхема имеет интерфейс для подключения внешней EEPROM памяти (выводы 1,2 и 32),
в которой могут храниться уникальные идентификационные коды производителя и типа устройства,
а также текстовые строки, содержащие информацию о наименовании, производителе устройства и т.п.
Микросхема может питаться либо от шины USB, либо от внешнего стабилизированного источника
питания с напряжением 5В. В качестве генератора тактовой частоты используется кварц на 6MHz,
подключаемый к выводам 27 и 28.

Микросхема имеет встроенный LDO-стабилизатор с выходным напряжением 3.3В, который служит для
питания внутренней логики, но может использоваться и для питания каких-либо внешних устройств
(вывод 6: 3V3OUT), правда, максимальный ток – всего 5мА. Уровень логической единицы на выходах
модуля UART микросхемы (выводы 16-25) задается напряжением на входе VCCIO (вывод 13) и может
изменяться в пределах от 3 до 5В (это нужно для обеспечения совместимости с 3-х вольтовой логикой).

Режим питания микросхемы определяется логическим уровнем на входе PWRCTL: лог.0 – питание
от шины USB (Bus-Powered), лог.1 – питание от стороннего источника (Self-Powered). Инверсный
вход RESET надо через резистор (а можно даже и без него) подтянуть к плюсу питания
микросхемы – этого достаточно для нормальной работы. Выход RSROUT может использоваться для
сброса внешних устройств в момент сброса FT232. Кроме того, к этому выводу подключается
подтягивающий резистор для линии USB Data . Сами линии Data и Data- – это выводы USBDP
и USBDM соответственно.

Замечу, что вход TEST должен быть соединен с землей, в противном случае работа микросхемы
нестабильна.

Выходы SLEEP и PWREN могут использоваться для управления внешними устройствами, в частности,
SLEEP = 0, если микросхема не активна («спит»), а PWREN = 0 после окончания инициализации
микросхемы при подключении и = 1, если микросхема не активна.

На выводах 16-25, как уже отмечалось, присутствуют все сигналы, предусмотренные стандартом RS-232.
При подключении к ним соответствующих микросхем-преобразователей уровня возможна конвертация
исходных данных, поступающих по USB, в поток байт протоколов RS-232, RS-422 или RS-485.

Прием и передача данных по USB могут отображаться светодиодами, подключенными к выходам
RXLED и TXLED соответственно.

Схема, которую я обычно использую, приведена на рисунке:

Как видите, навесных элементов очень немного. Для подключения к МК достаточно использовать
сигналы RX и TX, в ряде случаев может понадобиться организовать гальваническую развязку этой
схемы с остальным устройством. С учетом того, что преобразователь питается от USB, достаточно
добавить на RX и TX по оптрону, причем выход TX может напрямую управлять светодиодом одной
из оптопар.

В качестве EEPROM можно использовать микросхемы памяти 93С46/56/66, достаточно и самой
маленькой по объему:

С железом немножко разобрались, а что касается софта, то здесь тоже ничего сложного нет.
С сайта производителя
доступны для скачивания две версии драйвера: VCP Driver (только драйвер виртуального
СОМ-порта) и D2XX Driver (дает ряд дополнительных возможностей, например, прямой доступ
к USB – более интересен для программистов). Если планируется использование микросхемы
памяти – надо ставить D2XX, кроме того, понадобится также специальная утилита для прошивки:
MProg,
также доступная для скачивания с сайта. В остальном – подключаете устройство к ПК, говорите
ОС откуда взять драйверы, наблюдаете за установкой. В «Диспетчере устройств» в разделе «USB»
должен появиться «USB Serial Converter», а в «Портах» – следующий по номеру «USB Serial Port».
Все, можете с ним работать, как с обычным портом.

Так будет, если ваша FT232 работает без EEPROM (или с пустой) со стандартными VID&PID,
присвоенными производителем. Если с помощью MProg прошить в память новые VID&PID, наименование
устройства, его серийный номер и т.п., ваше устройство будет определяться уже совсем по-другому.
Как – вам виднее, наступает простор для творчества. Хотя я бы все таки не рекомендовал менять
стандартные VID&PID, а то получите сканер какой-нибудь… 😉

Я работал с этой микросхемой на скорости порта 115200, хотя драйвер позволяет выставлять
максимальную скорость до 921600. В разделе «Port Settings» свойств порта есть кнопка «Advanced».
Там в разделе «BM Options» параметр «Latency Timer» стоит поставить поменьше, т.е.
1мс – это увеличит скорость работы.

Если вы всерьез соберетесь использовать эту микросхему, советую ознакомиться с материалами
на странице ,
там действительного много полезного и интересного, а многие моменты объяснены гораздо подробнее,
чем в этой статье.

С TUSB3410 все будет несколько сложнее. Дело в том, что эта микросхема по своей
сути – микроконтроллер с интегрированным интерфейсным модулем USB. Поэтому, как всякий
микроконтроллер, ее придется еще и программировать…

Так получилось, что эта микросхема выпускается в таком же корпусе:

Функциональная оснащенность примерно та же: полный последовательный порт (выводы 13-21,
только RX/TX называются SIN/SOUT), интерфейс для EEPROM (здесь это I2C), кварц, питание,
Reset и четыре программируемые линии ввода/вывода общего назначения Р3.0 – Р3.4 – вот их-то
у FTDI-ки точно не было… Напряжение питания микросхемы – 3.3В, что не очень удобно,
поскольку при питании от USB заставляет использовать LDO-стабилизатор. Зато никаких хитрых
режимов питания нет.

Ну что, как всегда, кратенько пробежимся по функциональному назначению выводов? Поехали…

С последовательным портом все вроде бы понятно, скажу лишь, что при соответствующей прошивке
он может работать не только по протоколу RS-232, но и как IrDA приемопередатчик. Четыре линии
ввода/вывода тоже не экзотика, производитель, в частности, предоставляет пример, где они
используются для подключения нескольких кнопок, а устройство определяется ОС как HID-совместимое,
что позволяет достаточно легко реализовать опрос этих самых кнопок.

DP, DM – линии Data и Data- USB, PUR служит для подключения подтягивающего резистора для
линии Data .

На линию VDD18 надо подать напряжение 1.8В от внешнего источника или, что проще, подать лог.0
на вывод VREGEN, включив тем самым внутренний источник на 1.8В, а на VDD18 добавить конденсатор
0.1мкФ на землю…

На RESET – обычную RC-цепочку, более чем достаточно, TEST0 и TEST1 надо подтянуть через 10кОм
к питанию, а выход тактовой частоты CLKOUT мы использовать не будем.

Кварц 12МГц на ноги Х1 и Х2, выход индикатора SUSPEND – по вкусу, вход пробуждения WAKEUP можно
оставить неподключенным или подтянуть через резистор к плюсу питания.

К линиям последовательного порта этой микросхемы также можно подключать непосредственно МК,
опторазвязки или преобразователи уровней.

Вроде как все необходимое для минимальной конфигурации подключили, схема получилось вот такая:

Микросхема EEPROM здесь также не является обязательным элементом и лично я ее так ни разу и
не использовал…

Итак, собрали мы эту схему, воткнули в комп, ОС нашла некое устройство и попросила ткнуть ее
носом в драйвера для него. Вот тут-то самое интересное и начинается.

Как я уже говорил, TUSB3410 на самом деле микроконтроллер, внутри у него 8052-совместимое ядро.
Соответственно, функционал нашего устройства определяется залитой прошивкой. Нам требуется пока
что – преобразователь интерфейсов.

В принципе, при условии регистрации и предоставлении информации о вашем проекте производитель
предоставляет и исходники, и прошивку для применения микросхемы в качестве преобразователя
интерфейсов, но можно пойти и по другому пути. Эта микросхема используется в таком качестве
в некоторых интерфейсных шнурках для мобильных телефонов, в GSM-модемах, в некоторых других
устройствах. А драйвера для них доступны для свободной закачки. Более того, все эти драйвера
содержат необходимую прошивку. Это связано с особенностями работы микросхемы.

Дело в том, что при установке драйвера прошивка для микроконтроллера копируется в
/System32/drivers. Далее, при включении устройства TUSB проверяет наличие EEPROM и прошивки в ней.
Если все в порядке – грузится с нее, если нет – подгружает прошивку с компа и записывает в EEPROM,
если она есть. Или не записывает и просто работает. Если EEPROMа нет, при следующем включении
процесс повторяется.

В общем, в результате анализа нескольких комплектов драйверов к готовым устройствам методом
проб и ошибок, последовательных приближений и высоконаучного тыка был сгенерирован собственный
работоспособный комплект. Во всех файлах драйверов и даже внутри прошивок стоят копирайты
Texas Instruments, поэтому скажу, что все предпринятые над драйверами действия цели имели
исключительно образовательные и некоммерческие, а здесь результат публикуется сугубо для
ознакомления.

После подключения к ПК ОС найдет новое устройство «TUSB3410 Device» и потребует установку
драйвера, надо указать на файл umpusbXP.inf. В ходе установки в системную директорию будут
скопированы файлы umpusbxp.sys и umpf3410.i51 (прошивка). Далее система найдет виртуальный
СОМ-порт, для него потребуется драйвер UmpComXP.inf.

В обоих *.inf-файлах помечены строки, изменив которые можно отредактировать названия
определяемых системой устройств и установленные по умолчанию VID&PID, передаваемые ОС.
Однако, как и в прошлый раз, я бы не стал этого делать без полного понимания того, к чему
это может привести.

Для чего TUSB3410 нужна EEPROM я уже упоминал. Добавлю, что лично я с ней не экспериментировал,
однако на сайте производителя доступны для скачивания утилита для генерации бинарного файла
прошивки EEPROM на основе umpf3410.i51 и конфигурационного файла (содержит серийный номер
устройства, VID&PID, строковые данный по аналогии с FT232BM) и утилита для непосредственной
прошивки полученного файла в микросхему.

На странице,
посвященной этой микросхеме
при желании можно найти подробный даташит, ряд аппноутов,
документы, описывающие особенности применения, ссылки на исходные коды и утилиты для работы.
Настоятельно рекомендую посмотреть.

Результатом всех этих изысканий стало создание двух модулей преобразования протокола USB в RS-232,
на FT232BM и TUSB3410 соответственно, которые можно рассмотреть на фотографии:

Монтаж, как видно, поверхностный, все детали на одной стороне, с изнанки – пара перемычек.
Модули рассчитаны на вертикальное впаивание в плату, поэтому на них нет разъемов, а установлены
PLS штырьки, которые, собственно, в плату и впаиваются. На дальней от нас стороне платы
сделаны контактные площадки RX/TX (на модуле FT232BM их загораживает конденсатор), остальные
сигналы последовательного интерфейса не выведены за ненадобностью: эти модули используются для сопряжения
исключительно с МК.

Немножко возвращаясь к FT232BM. Ниже вы можете увидеть фото (а в конце статьи – скачать варианты разводки плат)
для двух конструкций на FT232BM с полным RS-232 портом.

В первой из них

cигналы RS-232 имеют TTL-уровень и выведены на двухрядный разъем BH-10 (по аналогии с материнскими платами),
причем коммутацией входа VCCIO (вывод 13) на 5В или сторонний источник 3.3В (в данном случае применен LDO-стабилизатор,
но можно, например, и параметрический использовать или регулируемый на LM317) при помощи джампера можно выбирать соответствующий
уровень логической “1” на выводах порта RS-232.
Эта конструкция разарабатывалась для отладки устройств, имеющих напряжение питания 3.3В

Еще один вариант модуля с полным RS-232 портом содержит в своем составе микросхему MAX213 – преобразователь уровней –
и, таким образом, по уровню напряжений обеспечивает совместимость с последовательными портами ПК.

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

А готовое устройство выглядит так:

Теперь о PL-2303: микросхема выпускается в 28-выводном SSOP корпусе с шагом выводов 0.65мм:

Микросхема во многом похожа на FT232, но есть и некоторые черты TUSB3410.
Для работы требуется кварцевый резонатор на 12 МГц (выводы 27-28), уровень логической единицы последовательного порта
определяется напряжением на входе VDD_232 (4), выводы 1-3, 5, 6 и 9-11 – полный последовательный порт.
По аналогии с TUSB3410 микросхема имеет пару выводов (13-14) для подключения EEPROM через I2C (память также служит для хранения
идентификаторов устройства).
Вход 23 определяет режим токовой нагрузки USB-порта (“1” – 500мА, “0” – 100мА), вход TRI-STATE определяет состояние выходов
последовательного порта при инициализации микросхемы: “1” – высокий уровень, “0” – высокоимпедансное состояние.
Отдельные входы питания для логики (8, 20) и PLL (24) в общем случае могут быть присоединены к шине питания USB, однако желательно
в непосредственной близости от них установить керамические конденсаторы на землю. PL-2303 имеет встроенные источник напряжения 3.3В
для питания USB-трансивера (вывод 17), который используется также для установки подтягивающего резистора к линии Data .
Как обычно, за более полным, точным и правильным описанием стоит обратиться на сайт производителя.
Схема модуля, разработанного на основе этой микросхемы, представлена на рисунке:

Фотографии готового устройства:

Как видно, этот модуль также содержит в своем сотаве микросхему MAX213 (SP213), поэтому обеспечивает поддержку полнофункционального
порта RS-232, совместимого по уровням с портами ПК.

Для полноценой работы этого устройства под управлением ОС семейства Windows понадобятся драйверы, доступные для скачивания
на сайте производителя. Поскольку на этих чипах
делается достаточно много интерфейсных шнурков для подключения мобильных телефонов к ПК, вполне вероятно, что подойдут
драйвера и от них.

Вопросы, как обычно, складываем тут.

Файлы:
Модуль FT232BM (формат SL5)
Модуль FT232BM EEPROM (формат SL5)
Модуль FT232BM EEPROM с 3.3В интерфейсом и полным портом RS-232 (SL5)
Модуль FT232BM EEPROM с полным RS-232 и MAX213 (плата SL5 и схема SPlan)
Модуль TUSB3410 (формат SL5)
Драйвер TUSB3410 (WinXP)
Схема и плата для PL-2303 (SL5)


3 заключение

Мы познакомились с теоретическими особенностями работы с USB устройствами в ОС Windows и Linux, а также получили практический навык работы с кроссплатформенной библиотекой libusb. Начерно, разработанное USB 3.0 устройство на базе контроллера FX обеспечивает требуемый поток данных для приёма данных из микросхемы ULPI без промежуточного их сохранения в SDRAM.

Оцените статью
Huawei Devices
Добавить комментарий