Интерфейс USB. Реализация, часть 1.


Продолжим цикл статей, посвященных интерфейсу USB. Как и договаривались, мы приступим к «практической» части – начнем писать код. В данной статье мы создадим программную прослойку, которая абстрагирует нас от «железа» (USB периферии конкретного МК). Это позволит нам в дальнейшем перейти к написанию высокого уровня «стека» USB (уровня обмена данными с хостом, обработки стандартных запросов USB и т. д.).

Небольшое лирическое отступление.

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

Пару слов о нашем МК – AT91SAM7S512. Чип как чип, агитировать использовать его я не буду (сам думаю о переходе на STM32 ), но и ругать чип тоже особо не буду. Производитель – ATMEL, ядро ARM7. По набору периферии, субъективно, незначительно превосходит ATmega128. Из приятных плюшек – чип можно программировать напрямую через USB. Соответственно, если данный интерфейс разведен на плате – можно обойтись без программатора.

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

На аппаратном уровне периферия поддерживает до четырех «конечных точек». Каждая конечная точка имеет свой аппаратный регистр управления/состояния и свои буферы для приема/отправки данных. Конечная точка с индексом 0 («нулевая конечная точка», та, которая нужна для управления) аппаратно «урезана» — размер буферов приема/отправки ограничен — 8 байт. Остальные конечные точки универсальны (могут быть любого типа) и имеют один буфер для отправки данных (размер буфера – 64 байта) и 2 буфера (2 банка) для чтения данных (по 64 байта каждый). Наличие двух банков для приема данных позволяет повысить скорость обмена: пока наша программа будет вычитывать данные из одного банка, периферия МК может принимать данные от хоста в другой. Переключать банки нужно вручную, через соответствующий регистр.

За USB интерфейсом МК закреплено оно аппаратное прерывание. Для того, чтобы внутри прерывания определить тип события, которое вызвало прерывание (источник), имеется соответствующий статусный регистр. В принципе, можно работать с USB «по опросу», без прерываний, но это как-то слишком «по-пионэрски», наш пример реализации будет работать «по прерыванию».

Теперь, немного «кепства» для облегчения дальнейшего понимания кода. В некоторых МК (например, ATmega) для работы с периферий (например, для «дерганья» ногой МК) используется один регистр управления, который доступен как для чтения, так и для записи. Соответственно, для того чтобы «дернуть» ногой нужно наложить на управляющий регистр соответствующую битовую маску через побитовое логическое «или» или «и».

В данном МК часто используется другой подход – для подобных операций МК имеет 2 регистра доступных только для записи. Для того чтобы «поднять» ногу нужно записать соответствующую битовую маску в первый регистр (SET регистр). Для того чтобы «опустить» эту же ногу — нужно записать ту же маску в другой (CLEAN) регистр. Для того чтобы определить текучее состояние – есть третий (GET) регистр, доступный только для чтения.

Стандартные отмазки. Мой код ужасен, не ругайте меня. Да нет, код как код :). Рефакторинг ему бы не помешал, но и создавался он не для конкурса «лучший код на С для МК». В коде в небольшом количестве присутствует «синтаксический сахар». В некоторых местах я сознательно отказался от обработки ошибок.

Переходим к реализации.

Начнем с высокоуровневого API нашего «стека». Так как наше устройство будет работать как виртуальный СОМ-порт, наше API будет состоять из трех функций: инициализация стека, прием данных и передача данных. В дальнейшем, программа МК будет использовать эти функции для обмена данными с ПК.


#define TRANSFER_STATUS_SUCCESS	0
#define TRANSFER_STATUS_ABORTED	1

typedef void (*TrasferCallback)(BYTE * buffer, WORD size, void * param, BYTE status);

void USB_Init(void);
DWORD USB_Read(BYTE * data, DWORD length, TrasferCallback callback, void * param);
DWORD USB_Write(const BYTE * data, DWORD length, TrasferCallback callback, void * param);


С функцией USB_Init() думаю все понятно — эта функция инициализирует соответствующую периферию МК, «запускает» стек и завершается после того, как хост закончит конфигурацию устройства (присвоит устройству адрес и т. д.).

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

В синхронном режиме в функцию передается указатель на буфер обмена данными (data) и размер буфера/размер данных в буфере (length). В качестве оставшихся параметров (callback, param) передается NULL. В таком режиме функция завершится после того как указанное количество байт будет получено/передано. Однако, пока функция будет ожидать завершения приема/передачи данных, будут «впустую» тратиться драгоценные мегагерцы МК :)

Более оптимальным, в некоторых случаях, будет работа со стеком в асинхронном режиме. В этом случае, в качестве параметра callback передается указатель на функцию, которая будет вызвана по завершению приема/передачи данных. Сама функция USB_Read()/USB_Write() завершится сразу после начала обмена данными, не дожидаясь завершения обмена.
Смысл остальных параметров будет понятен из реализации.

Определим несколько «дефайнов» для повышения читабельности кода


//адреса конечных точек
#define AT91C_EP_CFG 0
#define AT91C_EP_OUT 1
#define AT91C_EP_IN  2

//типы конечных точек
#define EP_TYPE_CFG	0
#define EP_TYPE_BULK	1
#define EP_TYPE_INT	2

//размеры аппаратного буфера для разных конечных точек
#define EP_BUFFER_SIZE			64
#define EP_CFG_BUFFER_SIZE		8

//логические состояния конечных точек
#define EP_STATUS_IDLE			0
#define EP_STATUS_READ			1
#define EP_STATUS_WTITE			2


Так как вся реализация будет «крутиться» вокруг конечных точек – объединим все переменные логически связанные с конечной точкой в структуру и напишем несколько простых inline функций для базовых операций с конечной точкой.


typedef struct {
	BYTE Type;		//тип конечной точки
	BYTE MaxSize;		//размер буфера конечной точки

	BYTE * Buffer;          //указатель на буфер для приема/отправки данных
	WORD Size;              //размер буфера/длина данных в буфере
	WORD BytesReady;        //количество обработанных (полученных/отправленных) байт
	WORD Bank;              //номер текущего банка для приема данных
	TrasferCallback Callback;       //указатель на Callback-функцию
	void * Param;           //дополнительные (пользовательские) параметры Callback-функции
	volatile BYTE Status;   //текущее логическое состояние конечной точки

	BYTE InterruptMask;     //маска для разрешения/запрещения прерываний конечной точки
	AT91_REG * CSR;         //указатель на аппаратный регистр управления конечной точки
	AT91_REG * FDR;         //указатель на аппаратный регистр данных конечной точки
} UsbEndpoint;

//Конечные точки
static UsbEndpoint Endpoint[4]; 

static UsbEndpoint * EndpointCfg	= &Endpoint[0];
static UsbEndpoint * EndpointOut	= &Endpoint[1];
static UsbEndpoint * EndpointIn		= &Endpoint[2];

//--- Сброс конечной точки (КТ)
static void EndpointReset(BYTE ep, BYTE Type, BYTE MaxSize) {

	//Если КТ находится в процессе асинхронного чтения/записи нужно сообщить приложению, что передача данных прервана
	if(Endpoint[ep].Status !=  EP_STATUS_IDLE && Endpoint[ep].Callback) Endpoint[ep].Callback(NULL, 0, NULL, TRANSFER_STATUS_ABORTED);

	Endpoint[ep].Type = Type;
	Endpoint[ep].MaxSize = MaxSize;

	//Сбрасываем все переменные в начальное значение
	Endpoint[ep].Buffer = NULL;
	Endpoint[ep].Size = 0;
	Endpoint[ep].ByteReady = 0;
	Endpoint[ep].Bank = AT91C_UDP_RX_DATA_BK0;
	Endpoint[ep].Callback = NULL;
	Endpoint[ep].Param = NULL;
	Endpoint[ep].Status = EP_STATUS_IDLE;

	//Сохраняем в структуре указатели на аппаратные регистры для данной КТ
	Endpoint[ep].InterruptMask = 1 << ep;
	Endpoint[ep].CSR = &AT91C_BASE_UDP->UDP_CSR[ep];
	Endpoint[ep].FDR = &AT91C_BASE_UDP->UDP_FDR[ep];

	//Аппаратно "передергиваем" КТ
	AT91C_BASE_UDP->UDP_RSTEP |= (1 << ep);
	AT91C_BASE_UDP->UDP_RSTEP &= ~(1 << ep);
}

//--- Установить биты по маске в аппаратном регистре управления КТ
__inline void EndpointSetFlag(UsbEndpoint * endpoint, DWORD flag) {
	*endpoint->CSR = *endpoint->CSR | flag;
	while((*endpoint->CSR & flag) == 0);
}

//--- Сбросить  биты по маске в аппартном регистре управления КТ
__inline void EndpointClearFlag(UsbEndpoint * endpoint, DWORD flag) {
	*endpoint->CSR = *endpoint->CSR & ~(flag);
	while((*endpoint->CSR & flag) != 0);
}

//--- Запретить прерывания для данной КТ
__inline void EndpointDisableInterrupt(UsbEndpoint * endpoint) {
	AT91C_BASE_UDP->UDP_IDR = endpoint->InterruptMask;
}

//--- Разрешить прерывания для данной КТ
__inline void EndpointEnableInterrupt(UsbEndpoint * endpoint) {
	AT91C_BASE_UDP->UDP_IER = endpoint->InterruptMask;
}

//--- Прочитать один байт данных из внутреннего буфера КТ
__inline BYTE EndpointGetData(UsbEndpoint * endpoint) {
	return *endpoint->FDR;
}

//--- Записать один байт данных во внутренний буфер КТ
__inline void EndpointPutData(UsbEndpoint * endpoint, BYTE data) {
	*endpoint->FDR = data;
}

//--- Прочитать аппаратный регистр статуса КТ
__inline DWORD EndpointGetCSR(UsbEndpoint * endpoint) {
	return *endpoint->CSR;
}

//--- Сбросить флаг "данные приняты" и переключить банк приемника
__inline void EndpointClearRxFlag(UsbEndpoint * endpoint) {
	if(endpoint->Type == EP_TYPE_CFG) {
		EndpointClearFlag(endpoint, AT91C_UDP_RX_DATA_BK0);
	} else {
		EndpointClearFlag(endpoint, endpoint->Bank);
		endpoint->Bank = (endpoint->Bank == AT91C_UDP_RX_DATA_BK0) ? AT91C_UDP_RX_DATA_BK1 : AT91C_UDP_RX_DATA_BK0;
	}
}

//--- Прием/передача завершены: вызвать Callback в асинхронном режиме, сменить логическое состояние КТ на EP_STATUS_IDLE
__inline void EndpointEndOfTransfer(UsbEndpoint * endpoint, BYTE status) {
	if(endpoint->Callback) endpoint->Callback(endpoint->Buffer, endpoint->BytesReady, endpoint->Param, status);
	endpoint->Status = EP_STATUS_IDLE;
}



Теперь можно начинать писать осмысленный код. Начнем с функции инициализации.


void USB_Init(void) {
	// Устанавливаем делитель и включаем тактирование USB периферии
	AT91C_BASE_CKGR->CKGR_PLLR |= AT91C_CKGR_USBDIV_1;
	AT91F_PMC_CfgSysClkEnableReg(AT91C_BASE_PMC, AT91C_PMC_UDP);
	AT91F_PMC_EnablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_UDP);

	//Сбрасываем номер текущей конфигурации
	CurrentConfiguration = 0;

	//Сбрасываем все КТ
	memset(Endpoint, 0x00, sizeof(Endpoint));
	EndpointReset(AT91C_EP_CFG, EP_TYPE_CFG, EP_CFG_BUFFER_SIZE);
	EndpointReset(AT91C_EP_OUT, EP_TYPE_BULK, EP_BUFFER_SIZE);
	EndpointReset(AT91C_EP_IN, EP_TYPE_BULK, EP_BUFFER_SIZE);

	//Разрешаем прерывания для "нулевой конечной точки"
	EndpointEnableInterrupt(EndpointCfg);

	//Устанавливаем обработчик и разрешаем прерывания USB контроллера
	AT91F_AIC_ConfigureItH(AT91C_BASE_AIC, AT91C_ID_UDP, AT91C_AIC_PRIOR_LOWEST, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, &USB_IrqHandler);
	AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_UDP);

	//Ждем, пока хост не завершит конфигурацию
	while(!CurrentConfiguration);
}


Здесь следует пояснить, что такое CurrentConfiguration. Это глобальная переменная, значение которой (отличное от 0) мы присвоим из прерывания, когда хост назначит нам «USB конфигурацию». Но об этом позже.

Теперь функции чтения/записи.


DWORD _USB_Write(volatile UsbEndpoint * endpoint, const BYTE * pData, DWORD length, TrasferCallback callback, void * param) {

	//Заполняем структуру КТ
	endpoint->Buffer = (BYTE *) pData;
	endpoint->Size = length;
	endpoint->BytesReady = 0;
	endpoint->Callback = callback;
	endpoint->Param = param;
	endpoint->Status = EP_STATUS_WTITE;

	//Ждем, пока КТ будет готова к передаче
	while(*endpoint->CSR & AT91C_UDP_TXPKTRDY);
	//Записываем блок данных в аппаратный буфер КТ
	DWORD cpt = MIN(endpoint->Size - endpoint->BytesReady, endpoint->MaxSize);
	while (cpt--) *endpoint->FDR = endpoint->Buffer[endpoint->BytesReady++];
	//Устанавливаем флаг "данные готовы к отправке"
	EndpointSetFlag((UsbEndpoint *) endpoint, AT91C_UDP_TXPKTRDY);

	if(endpoint->Type != EP_TYPE_CFG) {
		//Разрешаем прерывания для данной КТ
		EndpointEnableInterrupt((UsbEndpoint *) endpoint);

		//Для синхронного режима - ожидаем завершения отправки
		if(!endpoint->Callback) while(endpoint->Status == EP_STATUS_WTITE);
	}

	return endpoint->BytesReady;
}

DWORD _USB_Read(volatile UsbEndpoint * endpoint, BYTE * pData, DWORD length, TrasferCallback callback, void * param) {

        //Заполняем структуру КТ
        endpoint->Buffer = pData;
        endpoint->Size = length;
        endpoint->BytesReady = 0;
        endpoint->Callback = callback;
        endpoint->Param = param;
	endpoint->Status = EP_STATUS_READ;

	if(endpoint->Type != EP_TYPE_CFG) {
                //Разрешаем прерывания для данной КТ
                EndpointEnableInterrupt((UsbEndpoint *) endpoint);

                //Для синхронного режима - ожидаем завершения
                if(!endpoint->Callback) while(endpoint->Status == EP_STATUS_READ);
	}

        return endpoint->BytesReady;
}


Резюмируем: для того чтобы передать данные через конечную точку достаточно записать эти данные в аппаратный буфер конечной точки (но, нужно помнить о том, что размер буфера ограничен) и выставить в регистре управления флаг AT91C_UDP_TXPKTRDY. После того, как данные будут переданы хосту, произойдет аппаратное прерывание – можно передавать следующую порцию.

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

Как вы, наверное, поняли – все «мясо», вся логика работы USB реализована в обработчике прерывания. Это тема следующей статьи. После того, как мы разберемся с реализаций обработчика, я выложу исходные коды готового проекта.

В этой статье получилось «много кода, мало смысла». Ничего, в следующей произойдет слияние теории с практикой.
  • +4
  • 07 ноября 2011, 17:41
  • e_mc2

Комментарии (37)

RSS свернуть / развернуть
Эх, слишком близко к железу:)
Лучше бы про дескрипторы продолжили и про описание самого протокола обмнена.

Кстати, на STM32(даже на самых дешевых с USB — STM32F102) можно реализовать уже до 16 однонаправленных или 8 двунаправленных эндпоинтов.
Плюс размер буфера можно установить до 512байт(1кб).
Минус только в их стэке, очень неудобный.
0
  • avatar
  • iv_s
  • 07 ноября 2011, 18:02
К дескрипторам и протоколу мы еще вернемся :) Просто, после данной статьи можно сразу писать теорию + практическую реализацию.
0
Эх, слишком близко к железу:)
А как ты себе представляешь далекий от железа HAL?

Гм, а почему эндпойнты в комментариях везде названы чекпойнтами?
0
Заговариваться стал :) Сейчас исправлю.
0
Я имел в виду, что логичнее было бы сначала разобраться с протоколом, а потом уже переходить к примерам.
Т.е. сначала описание дескрипторов, control пакеты, flow диаграммы взаимодействия и т.п.

Ео в любом случае, это лишь мое субъективное мнение, автору виднее:)
0
Наверное, Вы правы. Просто мне не хочется описывать голую теорию. Есть, например, «USB in a NutShell», там теория изложена отлично, я им не конкурент :) Хотелось найти компромисс между теорией и практикой, но пока меня бросает то в одну сторону, то в другую.
0
Автор прав в своем подходе — тема очень большая, протокол очень кудрявый, без кода утонете. А тут все потихонечку. Если хочется теории, можно читать документацию, начните с usb in a nutshell(она и по русски есть, только перевод местами корявый), а потом спецификацию. Сам так продираюсь, очень тяжело :(
0
Мне так кажется, что код как раз усложняет понимание.
USB in a Nutshell — это вот эта?
www.beyondlogic.org/usbnutshell/usb1.shtml
Очень сложная для восприятия на мой вкус.
Мне куда больше понравилась USB Complete: The Developer's Guide.
0
Линк забыл.
0
На книжку то? Так легко гуглится. Я торрентах брал.
Один из: isohunt.com/torrents/?ihq=USB+Complete%3A+The+Developer%27s+Guide
0
0
Прежде всего, большое спасибо всем за поддержку и комментарии.
Выскажу свои соображения, как автор.
Изначально план был такой: сжато изложить основную теорию, быстро пробежаться по периферии МК, дойти до реализации высокого уровня, и дальше подробно описывать реализацию CDC, попутно «подтягивая» теорию. Это, правда, потребовало бы периодического припрыгивания с физического уровня на уровень протокола и на уровень логики, но зато дало бы минимальный, но достаточный уровень понимания интерфейса USB без «лишнего» углубления в теорию.
Теперь я не особо уверен в правильности подхода. Причины понятны – кому интересно углубляться в исходный код для МК, код который никогда не писал и писать не будешь?
С другой, стороны, описывать чистую теорию тоже особо смысла не вижу. Теория отлично изложена во многих источниках, и «конкурировать» с ними не вижу смысла – у меня не хватит ни знаний, ни усидчивости :) Да, и не думаю, что будет интересно читать «академическую» статью типа: «Пакеты бывают N типов. Каждый из них имеет признак начала, заголовок, контрольную суму и признак конца. Рассмотрим первый тип пакетов …»
Я попробую учесть все предложения, и, возможно, скорректирую свой первоначальный план. В любом случае, в цикле статей будет небольшой перерыв в связи с нехваткой свободного времени, но я обязательно продолжу цикл.
0
Нет, не так. Спросить надо: «Можно, я сделаю небольшой перерыв?» ))
0
по теме еще есть Агуров Павел — Интерфейс USB практика использования и программирования
0
А нельзя сначала теорию всю изложить, а потом примеры кодов с ссылками. По мне так алгоритмы нарисовать, вместо кода, таким образом можно хорошо раскрыть смысл происходящего. Полностью согласен с первым комментатором.
0
+1. Лучше писать на какомнить алгортмичном языке. Ибо, например, АВР от меня далеки и интересно это же для ПИКов. Стек то готовый есть, но он представляет из себя черный ящик, а интересно разобраться самому, постараться оптимизировать, поэкспериментировать с режимами.
0
А мне кажется что автор взял верное направление. Просто теории в сети много, а по данному примеру кода, точнее по коментариям к нему, вполне можно раскурить тот же PIC по аналогии…
Сухая теория по USB читается очень тяжело. Пока ее раскуришь — пропадает всякое желание чего-то делать…
0
Вопрос такой: Есть пример кейловсий HID Keil\ARM\Boards\Atmel\AT91SAM7S-EK\USB\HID

1. У кого есть программа для хоста?
0
  • avatar
  • VIC
  • 07 ноября 2011, 21:43
Хостом вроде как комп должен выступать. Какая еще нужна программа?
P.S. Может я глупый, но по моему вопрос непонятен и неконкретен…
0
Ну так под комп прога, для передачи в AT91SAM7.
0
Если пример на USB-CDC, то в системе появляется COM-порт, с которым работает любой эмулятор терминала — от Hyperterminal до Brays Terminal. Если не встал драйвер- надо подправить блокнотом в файле.инф драйвера VID и PID девайса для совместимости. Хороший пример есть в Сoocox для юсб, но использует библиотеки СОХ (за один вечер можно переписать под стандартные).
0
кому нужно см. Keil\ARM\Utilities\HID_Client
0
Здесь лежит AT91SAM7S-EK Software Package for IAR 5.2, Keil and GNU. Это «мегапак» (38 Мб) либ и примеров от atmel для данной борды. В т. ч. и USB HID под Keil.
0
Я поступил немного экстремально – взял ядро Linux, залез в исподники USB хоста и «понатыкивал» в определенных местах вывод дополнительной отладочной информации. После этого пересобрал ядро и проводил отладку на нем, анализируя логи ядра.



Сурово…

А за статью спасибо. Ждем продолжения!
0
  • avatar
  • _YS_
  • 07 ноября 2011, 21:46
Я же и говорю, что метод экстремальный :) На самом деле, код ядра Linux написан очень интуитивно понятно.
0
Не вижу в этом методе ничего экстремального. Я бы ещё логи бы через tail -f и grep пропускал, да в отдельный именованый канал кидал. А оттуда — в гуёвую прогу с шестнадцатеричным редактором.
0
EP0 используется только для установки и начальной инициализации или её также можно в дальнейшем использовать как буфер ввода\вывода?
0
Да, теоретически, EP0 можно использовать для передачи даннях.
0
в простых случаях обмена так и делается.
0
Никак не могу понять запись *endpoint->FDR = data. *endpoint-разименнование, а справа от ->, вроде, должен стоять указатель. Вопрос: как правильно прочитать данную запись? Это из-за того, что FDR тоже указатель?
0
Да, FDR — это указатель на регистр данных для данной конечной точки, соответственно *FDR = data – это запись в память по указателю (запись в регистр). Так как FDR храниться в структуре, на которую мы получили указатель, полная запись выглядит как *endpoint->FDR = data.
0
Это разыменнование FDR.
В C выражения разбираются по спирали, можно тут подробнее почитать: habrahabr.ru/blogs/cpp/100104/
Т.е. эта конструкция эквивалентна *(endpoint->FDR) = data.
А вобще, '->' это синтаксический сахар для '(*struct_ref_name).', так что вобще можно так переписать:)
*((*endpoint).FDR) = data
0
Не совсем понятен момент с описанием функций чтения\записи:
В прототипе указана:
DWORD USB_Read(BYTE * data, DWORD length, TrasferCallback callback, void * param);
Далее функция определяется как:
DWORD _USB_Read(volatile UsbEndpoint * endpoint, BYTE * pData, DWORD length, TrasferCallback callback, void * param)

Это все-таки разные функции? Тогда для каких целей используются функции, объявленные в прототипе?
0
Это разные функции.

_USB_Read – это внутренняя функция, она позволяет читать данные из произвольной КТ.
USB_Read – это внешняя функция, она читает данные из первой КТ (стандартом CDC эта КТ выделена под передачу данных от ПК к устройству).

На самом деле, в реализации все просто:


DWORD USB_Read( BYTE * pData, DWORD length, TrasferCallback callback, void * param) {
	if(!CurrentConfiguration) return 0;
	return _USB_Read(EndpointOut, pData, length, callback, param);
}
0
спасибо
0
Кто-нибудь может объяснить почему в условии проверки состояния КТ мы обращаемся к по указателю к функции, которая ничего не возвращает и при этом её вызов участвует в логическом выражении:
//Если КТ находится в процессе асинхронного чтения/записи нужно сообщить приложению, что передача данных прервана
        if(Endpoint[ep].Status !=  EP_STATUS_IDLE && Endpoint[ep].Callback) Endpoint[ep].Callback(NULL, 0, NULL, TRANSFER_STATUS_ABORTED);


То есть в условии if должно быть логическое выражение, а тут получается что EP_STATUS_IDLE логически умножают с указателем на функцию которая ничего не возвращает. Или я ошибаюсь?

Вот это условие тоже непонятно:

if(endpoint->Callback) 


Короче помогите разобраться в во всём этом.
0
В логическом выражении функция не вызывается. Это банальная проверка на ноль указателя на функцию.
А вообще, приличные люди хотя бы пишут ветку then на отдельной строке, а лучше — еще и в фигурных скобках. Так оно читается лучше.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.