Программирование ARM: работа с USB ARM7: техника использования виртуального последовательного порта USB CDC Fri, March 29 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ARM7: техника использования виртуального последовательного порта USB CDC Печать
Добавил(а) microsin   

Пример usb-device-cdc-serial-project (IAR) хорошо подходит для встраивания в свое разрабатываемое устройство. Это позволит реализовать удобную текстовую консоль управления, производить отладочный ввод/вывод (для чего обычно используют DBGU), просто реализовать передачу данных без необходимости писать дополнительный софт.

USB-COM.jpg

На рисунке показан обычный, всем известный переходник USB-COM, и пример usb-device-cdc-serial-project изначально ведет себя точно так же, как этот переходник. При подключении по USB переходника (или макетной платы, запрограммированной скомпилированным примером usb-device-cdc-serial-project) к компьютеру в операционной системе Windows появляется USB-устройство класса USB CDC, видимое в системе как обычный COM-порт, к которому можно подключиться простой терминальной программой (HyperTerminal, TerraTerm, SecureCRT, putty и проч.) для передачи данных или управления. Причем необязательно, что в устройстве USB CDC должен быть 9-штырьковый разъем DB9 male (у устройства USB CDC может быть только разъем USB) - обмен данными в этом случае будет происходить с программой, находящейся внутри разработанного устройства USB CDC.

Пример, о котором идет речь (в этой статье рассматривается проект для микроконтроллера AT91SAM7X128, AT91SAM7X256 или AT91SAM7X512, однако все что написано, вполне подходит для любого микроконтроллера Atmel серий ARM7 и ARM9), находится в папке C:\Program Files\IAR Systems\Embedded Workbench 5.4\arm\examples\Atmel\at91sam7x-ek\usb-device-cdc-serial-project, а все библиотеки в папке C:\Program Files\IAR Systems\Embedded Workbench 5.4\arm\examples\Atmel\at91lib. Далее для краткости эти папки я буду называть просто как usb-device-cdc-serial-project и at91lib.

Примечание: Вы можете столкнуться с зависанием при перетыкании кабеля USB - в файле at91lib\usb\device\core\USBD_UDP.c, подпрограмма USBD_Write, зависание в макросе SET_CSR - бесконечный цикл while ((AT91C_BASE_UDP -> UDP_CSR[endpoint] & (flags)) != (flags) );. Проблему можно решить, заменив макрос SET_CSR подпрограммой, где обрабатывается таймаут ожидания.

[Старый код USBD_UDP.c, который вызывал зависание]

#define SET_CSR(endpoint, flags) 
    { 
        volatile unsigned int reg; 
        reg = AT91C_BASE_UDP->UDP_CSR[endpoint] ; 
        reg |= REG_NO_EFFECT_1_ALL; 
        reg |= (flags); 
        AT91C_BASE_UDP->UDP_CSR[endpoint] = reg; 
        while ( (AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) != (flags)); 
    } 

[Новый код, заменяющий макрос SET_CSR, в котором баг исправлен, зависания нет]

//http://www.keil.com/forum/15376/
#define SET_CSR USBD_setCSR
boolean USBD_setCSR(unsigned char endpoint, unsigned int flags)
{
    volatile unsigned int reg;
    volatile unsigned int cnt = 100000;
    reg = AT91C_BASE_UDP->UDP_CSR[endpoint] ;
    reg |= REG_NO_EFFECT_1_ALL;
    reg |= (flags);
    AT91C_BASE_UDP->UDP_CSR[endpoint] = reg;
    while ( cnt > 0)
    {
        if ((AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) == flags)
        {
            break;
        }
        cnt--;
    }
    if (cnt > 0)
        return 1;
    else
        return 0;
}

Проект примера usb-device-cdc-serial-project представляет собой простой мост USB CDC <--> USART. После того, как скомпилируете пример и запишете его в микроконтроллер, можно подключать порт USB к компьютеру. Внимание: чтобы работала подсистема USB, частота кварца должна быть равна 18.432 МГц. После первого подключения к компьютеру на системе Windows мастер установки оборудования запросит драйвер для нового устройства, и ему нужно просто скормить информационный файл at91lib\usb\device\cdc-serial\drv\6119.inf. Теперь в системе обнаружится устройство AT91 USB to Serial Converter (COM5), и с ним можно работать как с обычным COM-портом.

usb-device-cdc-serial-project_AT91_USB_to_Serial_Converter_device.PNG

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

Это устройство ничего особенного не делает, просто тупо и прозрачно передает все данные, полученные от USB CDC, в физическое устройство USART микроконтроллера, и наоборот - все, что приходит на USART, прозрачно передается в виртуальный COM-порт USB CDC. Вы можете убедиться в этом сами, если подключитесь к порту COM5 программой - терминальным клиентом (TerraTerm, SecureCRT, putty и проч.), и замкнете друг на друга ножки PIN_USART0_RXD и PIN_USART0_TXD (порт PA0 и PA1, ножки 81 и 82 микроконтроллера AT91SAM7512 в корпусе LQFP100). Все, что вы введете на клавиатуре в окошке программы терминального клиента, при замыкании PIN_USART0_RXD на PIN_USART0_TXD будет возвращаться обратно.

Для того, чтобы использовать пример usb-device-cdc-serial-project в своей программе как управляющую консоль или для передачи данных, весь код настройки и использования USART нужно отключить (см. исходники по ссылке [1]). После этого поток данных через USB CDC можно использовать по своему усмотрению. Общая структура программного обеспечения firmware чипа выглядит примерно так:

ARM7_CDC_USB_IAR_library.png

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

[Инициализация CDCDSerialDriver]

Запуск драйвера USB CDC (CDCDSerialDriver) в firmware микроконтроллера целесообразно делать в момент подключения Вашего устройства к USB (если Ваше устройство уже запитано и делает какую-то работу помимо обработки USB). В этом случае для определения момента подключения к хосту USB для микроконтроллера должен быть предоставлен специальный сигнал, оповещающий о моменте подключения. Пример организации такого сигнала можно увидеть на схеме макетной платы AT91SAM7X (см. [2]), резисторы R10 и R12 формируют сигнал оповещения подключения к хосту USB_PR (заведен на порт микроконтроллера, настроенный как вход). Кроме того, необходимо управление нагрузочным резистором USB R11, сигнал USB_PUP (подключенный к порту микроконтроллера, настроенному как выход). При подключении USB код должен запустить драйвер USB CDC, и подключить R11 к +3.3 вольтам, чтобы дать хосту USB сигнал о подключении устройства USB.

usb-port-conn-example01.gif usb-port-conn-example02.gif

На схемах представлены варианты физической организации подключения порта USB. На схеме справа сигнал "5V Bus Monitoring" соответствует сигналу USB_PR, а сигнал "Pullup Control" соответствует USB_PUP (для управления транзистором требуется программная инверсия активного логического уровня).

Вот простейший пример в виде кусков кода (без анализа - что подключено, зарядное устройство или хост USB), который анализирует состояние сигнала USB_PR, запускает инициализацию USB CDC и управляет сигналом USB_PUP (код для примера с макетной платой AT91SAM7X, см. [2]).

//[Определения ножек]
#define PIN_INT_USB {1 << 26, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_INPUT, PIO_DEGLITCH}
#define PIN_USB_PULLUP {1 << 25, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_OUTPUT_0, PIO_DEFAULT}
const Pin inINT_USB = PIN_INT_USB;
const Pin pinPullUp = PIN_USB_PULLUP;

//[Начальная настройка ножек при подаче питания]
PIO_Configure(&inINT_USB, 1);
PIO_Configure(&pinPullUp, 1);

//[Опрос состояния ножки в главном цикле, и попытка запуска]
if (!PIO_Get(&inINT_USB))
{
  //Обнаружено подключение к разъему USB
  // либо компьютера, либо зарядного устройства.
  //Подключаем нагрузочный резистор R11 на 1.5 к
  PIO_Set(&outUSB_PULLUP);
  CDCDSerialDriver_Initialize();
}

//[Ожидание инициализации подключения в главном цикле]
usbsta = USBD_GetState();
if (usbsta >= USBD_STATE_CONFIGURED)
{
  //подключение успешно завершено, инициализация работы
  // буферов ввода/вывода USB CDC и запуск подсистем,
  // работающих с USB CDC на прием и передачу
  inRXCDC = 0;
  outRXCDC = 0;
  inTXCDC = 0;
  outTXCDC = 0;
  TXCDCdone = true;
  NeedInitCDCDSerialDriver_Read = true;
  ...
}

На диаграмме показан рекомендуемый общий принцип процедуры инициализации USB CDC. Участок кода от НАЧАЛО до КОНЕЦ должен прокручиваться в основном цикле main программы firmware пользователя.

CDCDSerialDriver_Initialize.png

Если код поддержки USB должен работать всегда, сразу после включения питания (например, когда Ваше устройство питается от USB, и работает всегда при включении питания), то тогда сигналы USB_PR и USB_PUP могут совсем не использоваться. Процедура инициализации драйвера USB должна запускаться сразу перед главным циклом main, анализ сигнала USB_PR не нужен. Нагрузочный резистор R11 может быть навсегда подключен к шине +3.3 вольт - тогда порт для управления USB_PUP также не нужен.

[Передача через USB CDC]

Неблокирующая передача осуществляется вызовом функции CDCDSerialDriver_Write (когда появились новые данные для передачи в главном кольцевом буфере, в нашем примере кода это TXCDC), после чего завершение передачи целесообразно отслеживать по функции обратного вызова (callback), в нашем примере это TXCDCcompleted. Адрес callback TXCDCcompleted передается как один из параметров функции CDCDSerialDriver_Write. Назначение остальных параметров очевидно. Как только передача завершится, TXCDCcompleted будет запущена и взведет флаг TXCDCdone, сигнализирующий о завершении передачи. Внимание! Флажок TXCDCdone должен быть обязательно сброшен до вызова CDCDSerialDriver_Write, иначе, если сделать наоборот, то это может стать причиной трудноуловимой ошибки - обработчик прерывания конечной точки (callback TXCDCcompleted) может сработать до сброса флажка TXCDCdone, и новая передача больше никогда не начнется. Чтобы не было путаницы: "прерывание" в этом контексте не относится к терминам USB (например, когда говорят о передачах Control, Interrupt, Isochronous, Bulk, то имеют в виду совсем другое), имеется в виду прерывание выполнения хода основной программы микроконтроллера. На диаграмме показан алгоритм передачи данных. Код между метками НАЧАЛО и конец должен вызываться в главном цикле main программы firmware пользователя.

CDCDSerialDriver_Write.png

Упрощенный пример кода передачи:

//размер главного буфера передачи
#define TXCDC_BUF_SIZE 1024
#define TXCDC_BUF_MASK (TXCDC_BUF_SIZE-1)
//временные переменные
u16 txbytes_to_send, txbufcnt;
//временный буфер для передачи данных CDCDSerialDriver_Write
u8 txcdcbuf[TXCDC_BUF_SIZE];
//главный кольцевой буфер передачи
u8 TXCDC[TXCDC_BUF_SIZE];
//индексы буфера TXCDC на ввод и вывод
u16 inTXCDC, outTXCDC;
//флажок завершения передачи
bool TXCDCdone;

//------------------------------------------------------------------------------
/// Callback для CDCDSerialDriver_Write,
/// запускается при завершении передачи
//------------------------------------------------------------------------------
static void TXCDCcompleted (void *pArg,
                            unsigned char status,
                            unsigned int transferred,
                            unsigned int remaining)
{
    //trace_LOG(trace_INFO, "TXCDCcompleted: %u\n\r", transferred);
    TXCDCdone = true;
}

//[Обработка передачи, прокручивается всегда из главного цикла]
//узнаем, сколько есть байт для передачи в главном
// кольцевом буфере. Код для подпрограммы idxDiff см. в [3].
txbytes_to_send = idxDiff (inTXCDC, outTXCDC, TXCDC_BUF_SIZE);
//ограничиваем размер блока передаваемых данных
// размером конечной точки (хотя, как ни странно,
// все работает нормально и без этого)
if (txbytes_to_send > BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAIN))
    txbytes_to_send = BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAIN);
//если можно передавать (TXCDCdone), и есть что передавать (txbytes_to_send),
// скопируем данные из кольцевого буфера TXCDC в txcdcbuf и запустим передачу
if (TXCDCdone && (0 != txbytes_to_send))
{
  for (txbufcnt = 0; txbufcnt   {
     txcdcbuf[txbufcnt] = TXCDC[outTXCDC++];
     outTXCDC &= TXCDC_BUF_MASK;
  }
  TXCDCdone = false; //запоминаем, что передача запущена
  CDCDSerialDriver_Write((void*)txcdcbuf, txbytes_to_send, TXCDCcompleted, 0);
}

Чтобы что-то передать в любое время, нужно просто положить данные побайтно в кольцевой буфер передачи TXCDC по индексу outTXCDC (подробнее про технику работы с кольцевым буфером см. в [3]):

TXCDC[outTXCDC++] = 'A';     //будет передана A
outTXCDC &= TXCDC_BUF_MASK;
TXCDC[outTXCDC++] = 'B';     //будет передана B
outTXCDC &= TXCDC_BUF_MASK;

[Прием через USB CDC]

Техника неблокирующего приема через CDC очень напоминает технику передачи. Для этого также есть функция запуска приема CDCDSerialDriver_Read, и запускаемый по окончании приема блока данных callback UsbCDCDataReceived. Адрес callback UsbCDCDataReceived точно так же передается в качестве параметра функции CDCDSerialDriver_Read. В нашем примере функция CDCDSerialDriver_Read запускается сразу после инициализации подключения интерфейса USB, а по завершении приема в вызове UsbCDCDataReceived заполняется кольцевой буфер приема RXCDC. В процессе запуска участвует флаг NeedInitCDCDSerialDriver_Read, установка которого сигнализирует о том, что текущий прием завершен и пора делать новый запуск приема. На диаграмме показан алгоритм приема данных. Код между метками НАЧАЛО и конец должен вызываться в главном цикле main программы firmware пользователя.

CDCDSerialDriver_Read.png

Упрощенный пример кода приема:

#define CDCDATABUFFERSIZE \ BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAIN)
//размер главного буфера приема
#define RXCDC_BUF_SIZE 256
#define RXCDC_BUF_MASK (RXCDC_BUF_SIZE-1)

//временный буфер для приема данных CDCDSerialDriver_Read
unsigned char usbCDCBuffer[CDCDATABUFFERSIZE];
//главный кольцевой буфер приема
u8 RXCDC[RXCDC_BUF_SIZE];
//индексы буфера RXCDC на ввод и вывод
u8 inRXCDC, outRXCDC;
//флажок завершения приема
bool NeedInitCDCDSerialDriver_Read;

//------------------------------------------------------------------------------
/// Callback, автоматически запускаемый по завершении приема
/// блока данных USB CDC. Принятые данные тупо копируются
/// в главный кольцевой буфер приема по индексу inRXCDC,
/// откуда потом данные могут быть легко прочитаны по индексу
/// outRXCDC.
//------------------------------------------------------------------------------
static void UsbCDCDataReceived(unsigned int unused,
                              unsigned char status,
                              unsigned int received,
                              unsigned int remaining)
{
    char *bufpnt;
    // Check that data has been received successfully
    if (status == USBD_STATUS_SUCCESS)
    {
        bufpnt = (char*)usbCDCBuffer;
        //trace_LOG(trace_INFO, "UsbDataReceived: %u\n\r", received);
        while (received)
        {
            RXCDC[inRXCDC++] = *bufpnt;
            inRXCDC &= RXCDC_BUF_SIZE;
            bufpnt++;
            received--;
        }
        // Check if bytes have been discarded
        if ((received == CDCDATABUFFERSIZE) && (remaining > 0))
        {
            trace_LOG(trace_INFO,
                     "UsbDataReceived: %u bytes discarded\n\r",
                     remaining);
        }
        NeedInitCDCDSerialDriver_Read = true;
    }
    else
    {
        trace_LOG(trace_INFO, "UsbDataReceived: Transfer error\n\r");
    }
}

//[Обработка приема, прокручивается всегда из главного цикла]
if (NeedInitCDCDSerialDriver_Read)
{
  NeedInitCDCDSerialDriver_Read = false;
  CDCDSerialDriver_Read(usbCDCBuffer,
                         CDCDATABUFFERSIZE,
                         (TransferCallback) UsbCDCDataReceived,
                         0);
}

Принимаемые данные могут быть прочитаны из кольцевого буфера RXCDC в любой момент по индексу outRXCDC, когда это удобно (подробнее про технику работы с кольцевым буфером см. в [3]). Внимание! Как и в случае передачи, флажок NeedInitCDCDSerialDriver_Read должен быть обязательно сброшен до вызова CDCDSerialDriver_Read, иначе, если сделать наоборот, то это может стать причиной трудноуловимой ошибки - обработчик прерывания конечной точки (callback UsbCDCDataReceived) может сработать до сброса флажка NeedInitCDCDSerialDriver_Read, и новый прием больше никогда не начнется.

[Отслеживание подключения и отключения терминала к виртуальному COM-порту]

Когда к виртуальному COM-порту подключается программа терминала (HyperTerminal, SecureCRT, TerraTerm, putty или аналогичная), то хост передает устройству USB запрос SETUP с установленным младшим битом (D0) в поле запроса wValue. Когда программа отключается (при этом COM-порт освобождается), то передается тот же самый запрос, но при этом младший бит в поле wValue сброшен. Подключение и отключение терминального клиента полезно отслеживать например для того, чтобы выдавать приветствие в консоли пользователя, или чтобы активизировать какую-либо аппаратуру (например, включать внешний модем).

Запросы SETUP обрабатываются в модуле CDCDSerialDriver.c подпрограммой CDCDSerialDriver_RequestHandler. Состояние бита D0 в поле wValue (бит D0 показывает, подключился или отключился терминал). Туда можно добавить нужный код, который будет запускаться при событиях подключения или отключения терминала, пример:

//------------------------------------------------------------------------------
/// Обрабатывает специфические для класса CDC запросы SETUP. Должен
/// вызываться из новой реализации метода USBDCallbacks_RequestReceived().
/// param указатель на экземпляр USBGenericRequest.
//------------------------------------------------------------------------------
void CDCDSerialDriver_RequestHandler(const USBGenericRequest *request)
{
    TRACE_INFO_WP("NewReq ");
 
    // Handle the request
    switch (USBGenericRequest_GetRequest(request))
    {
    case CDCGenericRequest_SETLINECODING:
        CDCDSerialDriver_SetLineCoding();
        break;
    case CDCGenericRequest_GETLINECODING:
        CDCDSerialDriver_GetLineCoding();
        break;
    case CDCGenericRequest_SETCONTROLLINESTATE:
        CDCDSerialDriver_SetControlLineState(
            CDCSetControlLineStateRequest_ActivateCarrier(request),
            CDCSetControlLineStateRequest_IsDtePresent(request));
        //НАЧАЛО добавленного кода
        if (CDCSetControlLineStateRequest_IsDtePresent(request))
        {
            //Произошло подключение к виртуальному COM-порту программой
            // терминального клиента (putty).
            ModemON();
            Greeting();
        }
        else
        {
            //Терминальный клиент отключился.
            ModemOFF();
        }
        //КОНЕЦ добавленного кода
        break;
    default:
        USBDDriver_RequestHandler(&(cdcdSerialDriver.usbdDriver), request);
        break;
    }
}

[Тестирование]

В примере кода usb-device-cdc-serial-project-looped по ссылке [1] прием программно закольцован на передачу с целью тестирования. Максимальная скорость передачи и приема, которую показала программа [4], составила 11.26 килобайта/сек при настройке COM-порта USB CDC на скорость 115200 бод, 8 бит данных, без четности, 1 стоп-бит (реальная скорость составила примерно 92240 бит/сек). Получилась меньше 115200 бит/сек из-за накладных расходов при перезапуске приема и передачи, а так же из-за наличия стартового и стопового битов в физическом потоке данных. Можно за один раз передавать порцию данных блоком любого размера до 4096 байт.

COM-Port-Stress-Test01.PNG


AT91 USB CDC Driver Implementation (перевод даташита Atmel doc6269.pdf)

[1. Введение]

Класс USB Communication Device Class (CDC) предназначен главным образом для осуществления всех типов сетевых коммуникаций по шине Universal Serial Bus (USB). Этот класс делает возможным подключить телекоммуникационные устройства наподобие телефонов и аналоговых модемов, а также сетевые устройства типа кабельных или ADSL модемов.

В то время как устройство CDC включает реализацию довольно сложных устройств, класс CDC может использоваться в качестве очень простого метода для передачи данных через USB. Например, устройство CDC может появиться на компьютере как virtual COM port (VCP, виртуальный COM-порт), который значительно упрощает написание программ для компьютера (ПО хоста).

В этом документе показано, как реализовать CDC на платформе AT91 ARM® Thumb® микроконтроллеров с использованием AT91 USB Framework компании Atmel. Для этой цели будет предоставлена пошаговая реализация примера конвертера USB-to-Serial.

[2. Документы, которые упомянуты в статье]

1. Universal Serial Bus Class Definitions for Communication Devices, Version 1.1, January 19, 1999.
2. Atmel Corp., AT91 USB Framework, 2006.

[3. Основы Communication Device Class]

Эта секция предоставляет базовую информацию о Communication Device Class - когда он используется, какие для него имеются драйверы. Дается также описание архитектуры.

3.1 Назначение CDC

CDC используется для подключения коммуникационных устройств - модемов (цифровых и аналоговых), телефонов или сетевых устройств (сетевые адаптеры NIC). CDC является стандартной средой, поддерживающей широкий диапазон физических слоев (xDSL, ATM, и т. п.) и протоколов.

В этом документе CDC используется для реализации конвертера USB в последовательный порт. Традиционные последовательные порты (также известные как COM или RS-232) все еще используются очень широко, однако современные компьютеры PC (особенно ноутбуки) сейчас поставляются без последовательных портов. Преобразователь USB-to-serial может использоваться в этом случае как мост между традиционным RS-232 и портом USB.

[3.2 Архитектура]

3.2.1 Интерфейсы

Стандартом CDC (CDC specification 1.1) определено два интерфейса. Первый из них, Communication Class Interface, используется для управления устройством. Он включает в себя запросы для управления состоянием устройства, его ответы, а также оповещения о событиях. Этот интерфейс может иногда использоваться для управления вызовами, например установкой и завершением соединений, а также для настройки их параметров.

Другой интерфейс определен для простой передачи данных. Он носит название Data Class Interface. Этот интерфейс предоставляет способ обмена данными между коммуникационным устройством и хостом (компьютером). Дополнительно этот интерфейс разрешает мультиплицирование данных и команд поверх одного канала, путем использования оберток (wrappers).

3.2.2 Конечные точки (endpoints)

Communication Class Interface требует для себя как минимум одну конечную точку, которая задействована для управления устройством. Для этого используется конечная точка по умолчанию номер 0 (default control endpoint). Дополнительно (что необязательно) может выделена другая конечная точка для оповещения о событиях (events notification). Обычно это конечная точка типа Interrupt IN. Чтобы не было путаницы: термин interrupt (прерывание) в данном контексте означает просто некую сущность протокола USB (это не прерывание микроконтроллера).

Для Data Class Interface должна быть конечные точки в парах одного и того же типа. Это нужно для обмена в обе стороны - Data IN (от устройства к хосту) и Data OUT (от хоста к устройству). Для этой цели могут использоваться только конечные точки типа Bulk и Isochronous.

Рис. 3-1. Архитектура драйвера класса CDC

AT91-CDC-Class-Driver-Architecture-fig-3-1

Под Host подразумевается хост USB (компьютер). На голубом фоне в схеме показаны части драйвера, реализованные в firmware микроконтроллера AT91. Под Device подразумевается устройство USB класса CDC. Data Class Interface (Data IN, Data OUT) и Communication Class Interface (Control 0, Interrupt IN) - логическая абстракция интерфейсов, реализованная программно в firmware микроконтроллера поверх конечных точек UDP. Control 0 - управляющая конечная точка 0 (default control endpoint), Data IN, Data OUT, Interrupt IN - конечные точки UDP соответствующего типа. Communication peripheral - аппаратура микроконтроллера (для программиста это регистры UDP), которая осуществляет поддержку протокола USB.

3.2.3 Модели

Чтобы учесть все разнообразие коммуникационных устройств, определено несколько моделей. Они описывают требования в терминах интерфейсов, конечные точки и запросы, которым устройство должно удовлетворять для выполнения свой определенной роли. Здесь приведен список моделей из CDC specification 1.1, сгруппированный по их функциональности (назначению устройств):

POTS (Plain Old Telephone Service, традиционная аналоговая телефонная служба)
   - Direct Line Control Model (модель прямого управления линией связи)
   - Datapump Model (модель передачи данных)
   - Abstract Control Model (абстрактная модель управления)
Telephone (телефония)
   - Telephone Control Model (модель управления телефоном)
ISDN (стандарт цифровой телефонии)
   - Multi-Channel Model (многоканальная модель)
   - USB CAPI Model
Networking (сетевые коммуникации)
   – Ethernet Networking Model (сетевая модель Ethernet)
   - ATM Networking Control Model (сетевая модель управления ATM)

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

3.2.4 Class-Specific Descriptors (специфические дескрипторы класса CDC)

Информация, касающаяся класса CDC, описана в Functional Descriptors (дескрипторы функции). Они задают различные параметры интерфейса - как устройство поддерживает управление вызовами, различные атрибуты, специфичные для модели.

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

3.3 Host Drivers (драйверы хоста)

Большинство операционных систем (Operating Systems, OS) сегодня включают в себя драйверы для широкого спектра классов USB. Это упрощает разработку устройства USB, поскольку половину сложной работы выполняет системное программное обеспечение OS хоста. Производители могут сконцентрироваться только на реализации устройства USB, и им не нужно разрабатывать специальные драйвера для хоста.

Здесь приведен список различных реализаций CDC, поддерживаемых некоторыми OS:

• Windows®
– Abstract Control Model
– Remote NDIS

• Linux®
– Abstract Control Model
– Ethernet Model

[4. USB to Serial Converter (конвертер USB - последовательный порт)]

Эта секция описывает реализацию конвертера USB-to-serial, с использованием класса CDC и библиотеки AT91 USB Framework. За подробностями обращайтесь к документу Atmel № 6263 для информации по фреймворку USB framework, и к стандарту CDC Specification 1.1 и USB Specification 2.0 для получения информации, связанной с USB/CDC.

4.1 Предназначение

Несмотря на то, что USB все больше используется в новых продуктах, много старых устройств все еще используют традиционный RS-232 (который также называют COM-порт) для соединения с PC. Этот старый интерфейс все еще широко распространен, однако большинство производителей компьютеров и материнских плат начали поставлять свои машины без COM-порта. Конвертер USB-to-serial может быть использован для добавления к компьютеру виртуального COM-порта (virtual COM port), работающего поверх USB, что позволить подключить устройства по традиционному интерфейсу RS-232.

Виртуальный COM-порт может также использоваться для обеспечения подключения к USB-устройству старыми программами PC. Это также простой метод для коммуникаций через USB, при котором не нужно писать собственные драйвера (и иногда не нужно даже писать ПО хоста).

Рис. 4-1. Мост между традиционным (Legacy Device, в котором стоит RS-232) устройством  и компьютером с конвертером USB-to-Serial

AT91-CDC-bridging-fig-4-1

4.2 Архитектура

Среда разработки AT91 USB Framework предоставляет API, которое упрощает разработку драйверов класса USB со стороны устройства USB. В этом апноуте приведен пример программы на основе AT91 USB Framework. На рис. 4-2 показана архитектура программы на основе этого фреймворка.

Рис. 4-2. Архитектура программного обеспечения с использованием AT91 USB Framework

AT91-CDC-Software-Architecture-fig-4-2

4.3 Модель

Стандарт CDC задает модель, которая отлично подходит для нашего приложения: Abstract Control Model (ACM). Она реализует запросы и оповещения, необходимые для коммуникации с интерфейсом RS-232.

Abstract Control Model требует двух интерфейсов, Communication Class Interface и Data Class Interface. Каждый из них должен иметь две привязанные конечные точки. Поэтому нужно выделить одну конечную точку, выделенную для управления устройством (default Control endpoint 0) и одну для уведомлений о событиях (дополнительная конечная точка типа Interrupt IN).

Data Class Interface также нуждается в двух конечных точках, через которые будут переноситься данные к хосту и от хоста. В зависимости от приложения, эти конечные точки могут быть либо типа Bulk, либо типа Isochronous. В случае конвертера USB-to-serial использование конечных точек Bulk может быть более подходящим, поскольку важна надежность передачи данных, и передача данных не критична по времени.

4.4 Дескрипторы

Для конвертера USB-to-serial используются стандартные дескрипторы, такие как например используются в USB Specification 2.0. Следующие примеры кода используют эти структуры, описанные в апноуте AT91 USB Framework.

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

• CDCHeaderDescriptor
• CDCCallManagementDescriptor
• CDCAbstractControlManagementDescriptor
• CDCUnionDescriptor

4.4.1 Device Descriptor (дескриптор устройства)

The Device Descriptor должен указать значение 02h в своем поле bDeviceClass, соответствующие классу Communication Device Class. Это нужно, чтобы драйвер хоста корректно провел энумерацию устройства: поскольку устройство CDC часто показывает больше одного интерфейса, они должны быть логически сгруппированы друг с другом, чтобы они функционально не рассматривались как раздельные.

Для CDC не определены коды подкласса (subclass codes) или коды протокола (protocol codes).

Так будет выглядеть дескриптор устройства при использовании структуры S_usb_device_descriptor structure библиотеки AT91 USB Framework:

const USBDeviceDescriptor deviceDescriptor =
{
   sizeof(USBDeviceDescriptor),
   USBGenericDescriptor_DEVICE,
   USBDeviceDescriptor_USB2_00,
   CDCDeviceDescriptor_CLASS,
   CDCDeviceDescriptor_SUBCLASS,
   CDCDeviceDescriptor_PROTOCOL,
   BOARD_USB_ENDPOINTS_MAXPACKETSIZE(0),
   CDCDSerialDriverDescriptors_VENDORID,
   CDCDSerialDriverDescriptors_PRODUCTID,
   CDCDSerialDriverDescriptors_RELEASE,
   0, // нет строкового дескриптора для производителя
   1, // индекс строки продукта равен 1
   0, // нет строкового дескриптора для серийного номера
   1  // устройство имеет только 1 возможную конфигурацию
};

Поля Vendor ID (идентификатор вендора) и Product ID (идентификатор продукта) используются драйвером для процесса энумерации устройства. Значение Vendor ID предоставляется производителю организацией USB-IF; значение Product ID производитель назначает сам. В нашем примере реализации используется Atmel vendor ID (03EBh) вместе с отдельным product ID (6119h).

4.4.2 Configuration Descriptor (дескриптор конфигурации)

По запросу хоста устройство configuration descriptor, за которыми идут дескрипторы интерфейса, конечных точек и дескрипторы, специфичные для класса (interface, endpoint и class-specific descriptors). В то время как стандарт CDC не определяет специальных значений для дескриптора конфигурации, ряд специфичных для класса дескрипторов указан. Они упомянуты как Functional Descriptors (функциональные дескрипторы), и некоторые из них должны быть реализованы.

// Стандартный дескриптор конфигурации
{
   sizeof(USBConfigurationDescriptor),
   USBGenericDescriptor_CONFIGURATION,
   sizeof(CDCDSerialDriverConfigurationDescriptors),
   2, // В этой конфигурации есть два интерфейса
   1, // Это конфигурация №1
   0, // Для этой конфигурации нет строкового дескриптора
   BOARD_USB_BMATTRIBUTES,
   USBConfigurationDescriptor_POWER(100)
},

4.4.3 Communication Class Interface Descriptor (дескриптор интерфейса коммуникационного класса)

Первый дескриптор интерфейса, который идет за дескриптором конфигурации, должен быть Communication Class Interface descriptor. Он должен указать код Communication Class Interface (02h) в своем поле bInterfaceClass.

Значение bInterfaceSubClass выбирает модель CDC, используемую интерфейсом. В этом случае будет код 02h, соответствующий Abstract Control Model.

И наконец, нужен предоставленный код протокола. Поскольку он не требуется для конвертера USB-to-serial, то он может быть оставлен в значении 0x00.

// Стандартный дескриптор Communication class interface
{
   sizeof(USBInterfaceDescriptor),
   USBGenericDescriptor_INTERFACE,
   0, // это интерфейс №0
   0, // это альтернативная настройка №0 для этого интерфейса
   1, // этот интерфейс использует конечную точку 1
   CDCCommunicationInterfaceDescriptor_CLASS,
   CDCCommunicationInterfaceDescriptor_ABSTRACTCONTROLMODEL,
   CDCCommunicationInterfaceDescriptor_NOPROTOCOL,
   0 // для этого интерфейса нет строкового дескриптора
},

В то время как Communication Class Interface использует две конечные точки (одну для управления устройством, и другую для оповещения о событиях), дескриптор интерфейса должен иметь свое поле bNumEndpoints установленным в 0x01: default control endpoint 0 не считается.

4.4.4 Functional Descriptors (функциональные дескрипторы)

За дескриптором интерфейса коммуникационного класса (Communication Class Interface Descriptor) должны идти несколько функциональных дескрипторов (Functional Descriptors). Они нужны для определения некоторых атрибутов устройства. Структура функционального дескриптора содержит длину дескриптора, тип и подтип, за которыми идет функциональная информация.

Значение bDescriptorType всегда равно CS_INTERFACE (24h), поскольку CDC-специфичные дескрипторы прикладываются только к интерфейсам. Значения для двух других полей, bFunctionLength и bDescriptorSubType, зависят от функций.

4.4.4.1 Header (заголовок)

Первый функциональный дескриптор должен всегда быть типа Header Functional Descriptor (функциональный дескриптор с заголовком). Он используется для указания версии CDC, на которой базируется устройство (текущая версия 1.1):

// Class-specific header functional descriptor
{
   sizeof(CDCHeaderDescriptor),
   CDCGenericDescriptor_INTERFACE,
   CDCGenericDescriptor_HEADER,
   CDCGenericDescriptor_CDC1_10
},

4.4.4.2 Call Management (управление вызовами)

Далее идет Call Management Functional Descriptor. Он показывает, как устройство обрабатывает вызовы. Если устройство само обрабатывает вызовы, то первый бит поля bmCapabilities должен быть установлен в 1. В дополнение запросы о вызовах могут быть мультиплицированы поверх data class interface вместо отправки их через Control endpoint 0, путем установки второго бита. В соответствии со стандартом CDC устройство, использующее Abstract Control
Model, должно обрабатывать вызовы само, так что бит D0 будет установлен. Последний байт (bDataInterface) не будет ничего обозначать, поскольку очищен бит D1 в поле bmCapabilities.

// Class-specific call management functional descriptor
{
   sizeof(CDCCallManagementDescriptor),
   CDCGenericDescriptor_INTERFACE,
   CDCGenericDescriptor_CALLMANAGEMENT,
   CDCCallManagementDescriptor_SELFCALLMANAGEMENT,
   0 // нет связанного интерфейса данных
},

4.4.4.3 Abstract Control Management (ACM, абстрактная модель управления)

Поскольку конвертер USB-to-serial использует Abstract Control Model, должен быть передан соответствующий функциональный дескриптор (Abstract Control Management Functional Descriptor) для предоставления дополнительной информации - на каких запросах/оповещениях реализовано устройство. Для этого примера, драйвер делает поддержку всех дополнительных запросов/оповещений (requests/notification) за исключением NetworkConnection; таким образом, поле bmCapabilities должно быть установлено в значение 07h.

// Class-specific abstract control management functional descriptor
{
   sizeof(CDCAbstractControlManagementDescriptor),
   CDCGenericDescriptor_INTERFACE,
   CDCGenericDescriptor_ABSTRACTCONTROLMANAGEMENT,
   CDCAbstractControlManagementDescriptor_LINE
},

4.4.4.4 Union (объединение)

И наконец, Union Functional Descriptor делает возможным сгруппировать несколько интерфейсов в одну глобальную функцию. В этом случае Communication Class Interface будет установлен как главный интерфейс в группе, а Data Class Interface будет подчиненным как slave 0:

// Class-specific union functional descriptor с одним подчиненным интерфейсом
{
   sizeof(CDCUnionDescriptor),
   CDCGenericDescriptor_INTERFACE,
   CDCGenericDescriptor_UNION,
   0, // номер главного интерфейса (master interface) =0
   1  // первый подчиненный интерфейс (slave interface) =1
},

4.4.5 Notification Endpoint Descriptor (дескриптор конечной точки оповещения)

Как было сказано ранее, в модели Abstract Control Model используется элемент оповещения о событиях (notification) на конечной точке Interrupt IN. Атрибуты пользователя для неё - адрес конечной точки и интервал опроса.

Когда выбирается адрес конечной точки, должна быть учтена специфика контроллера USB. Например, для чипов AT91SAM7S контроллер UDP имеет только 4 конечные точки, одна из которых используется для default Control endpoint 0. Поскольку только 2 и 3 конечная точки имеют двойные банки FIFO, более мудро будет использовать их для Data Class Interface и последнюю использовать для оповещения о событиях.

В заключение должна быть установлена скорость опроса (polling rate) на источнике прерываний. Для нашего случая будет использоваться USART. Поскольку это не очень быстрый интерфейс, то скорость опроса может быть быть относительно низкой, т. е. значение bInterval может быть большим.

Так будет выглядеть notification endpoint descriptor, заданный в нашем примере firmware:

// Стандартный Notification endpoint descriptor
{
   sizeof(USBEndpointDescriptor),
   USBGenericDescriptor_ENDPOINT,
   USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_IN,
   CDCDSerialDriverDescriptors_NOTIFICATION),
   USBEndpointDescriptor_INTERRUPT,
   MIN(
      BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_NOTIFICATION),
      USBEndpointDescriptor_MAXINTERRUPTSIZE_FS
      ),
   10    // конечная точка будет опрашиваться на наличие события каждые 10 мс
},

4.4.6 Data Class Interface Descriptor (дескриптор интерфейса класса данных)

За дескриптором Communication Class Interface и связанными функциональными дескрипторами и дескрипторами конечных точек идет Data Class Interface Descriptor.  Сам по себе Data Class Interface имеет идущие за ним два дескриптора конечной точки.

Код Data Class Interface равен 0Ah, и здесь нет кода подкласса (subclass code). Доступны некоторые коды протоколов, но в нашем примере они не используются. Однако мог использоваться v.42bis протокол для сжатия данных, если поддерживается устаревшее устройство.

// Стандартный дескриптор Data class interface
{
   sizeof(USBInterfaceDescriptor),
   USBGenericDescriptor_INTERFACE,
   1, // это интерфейс 1
   0, // это альтернативная установка 0 для этого интерфейса
   2, // этот интерфейс использует 2 конечные точки
   CDCDataInterfaceDescriptor_CLASS,
   CDCDataInterfaceDescriptor_SUBCLASS,
   CDCDataInterfaceDescriptor_NOPROTOCOL,
   0 // для этого интерфейса нет строкового дескриптора
},

4.4.7 Data IN & OUT Endpoint Descriptors (дескрипторы для конечных точек данных IN и OUT)

Data Class Interface требует две дополнительные конечные точки, так что далее должны идти дескрипторы конечных точек (Endpoint Descriptors). Мы ранее решили, что это будут конечные точки Bulk IN/OUT, поскольку они больше подходят для этого частного приложения.

Так как адреса 00h и 03h уже заняты для default Control endpoint 0 и Interrupt IN notification endpoint соответственно, то data OUT и IN endpoints получат адреса 01h и 02h.

Вот два дескриптора Data IN & OUT Endpoint:

// Стандартный дескриптор Bulk-OUT endpoint
{
   sizeof(USBEndpointDescriptor),
   USBGenericDescriptor_ENDPOINT,
   USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_OUT,
   CDCDSerialDriverDescriptors_DATAOUT),
   USBEndpointDescriptor_BULK,
   MIN(BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAOUT),
   USBEndpointDescriptor_MAXBULKSIZE_FS),
   0 // должен быть 0 для полноскоростных (full-speed) bulk endpoints
},
// Стандартный дескриптор Bulk-IN endpoint
{
   sizeof(USBEndpointDescriptor),
   USBGenericDescriptor_ENDPOINT,
   USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_IN,
   CDCDSerialDriverDescriptors_DATAIN),
   USBEndpointDescriptor_BULK,
   MIN(BOARD_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAIN),
   USBEndpointDescriptor_MAXBULKSIZE_FS),
   0 // должен быть 0 для полноскоростных (full-speed) bulk endpoints
},

4.4.8 String Descriptors (строковые дескрипторы)

Некоторые дескрипторы (устройства, конфигурации, интерфейса и т. п.) могут указать индекс строкового дескриптора для комментариев по их использованию. Эти строки полностью определяются пользователем (программистом firmware устройства USB), и они никак не влияют на работу драйвера (на выбор настроек при энумерации, к примеру) в операционной системе для этого USB-устройства.

4.5 Class-specific Requests (запросы, специфичные для класса)

Стандарт CDC задает набор специальных запросов этого класса для устройств, реализующих Abstract Control Model. В этой секции описываются детально эти запросы, включая их использование и реализацию. Для дополнительно информации по Abstract Control Model Serial Emulation и соответствующим запросам и оповещениям см. секцию 3.6.2.1 CDC specification 1.1.

4.5.1 SendEncapsulatedCommand, GetEncapsulatedResponse

4.5.1.1 Назначение

Эти два запроса используются когда применяется частный протокол управления с communication class interface. Для virtual COM port это не тот случай, так что они не должны быть реализованы, даже при том, что они предположительно обязательны. Практически эти запросы никогда не будут приняты.

4.5.2 SetCommFeature, GetCommFeature, ClearCommFeature

4.5.2.1 Назначение

Запросы Set/Get/ClearCommFeature используются для модификации некоторых атрибутов коммуникации, а также для получения их значений.

Первый атрибут, использующийся в настоящее время, это код страны Country Code. Некоторые устройства работают по-разному, или имеют разные законные ограничения в зависимости от страны, где они работают. Таким образом, код страны нужен для идентификации соответствующих параметров. Однако, это не нужно для конвертера USB-to-serial, поскольку он не соединяется в национальной или зависящей от страны сетью.

Abstract State (абстрактное состояние) устройства можно также изменить через использование этого запроса. Первая особенность, которую можно поменять - нужно ли мультиплицировать данные и вызовы через data class interface. Поскольку это уже указано в функциональном дескрипторе управления вызовом (см. секцию 4.4.4.2), то в нашем примере это не поддерживается, так как не имеет никакого смысла.

Idle state (состояние ожидания) устройства можно также переключить через этот запрос. В состоянии ожидания (idle) устройство не должно принимать или отправлять данные от хоста и к хосту.

4.5.3 SetLineCoding, GetLineCoding

4.5.3.1 Назначение

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

• Baudrate (скорость передачи данных)
• Number of stop bits (количество стоп-битов)
• Parity check (наличие бита контроля четности)
• Number of data bits (количество бит данных)

Когда программа терминала (ПО хоста наподобие HyperTerminal) меняет установки COM-порта, то отправляется запрос SetLineCoding с новыми параметрами. Хост может также запросить текущие настройки запросом GetLineCoding, и не менять их, если настройки корректны.

4.5.3.2 Реализация

Когда принят запрос SET_LINE_CODING, то устройство должно сначала прочитать новые параметры, которые находятся в структуре из 7 байт (эта структура описана в CDC specification 1.1, секция 6.2.13). Затем устройство должно запрограммировать эти новые параметры в порт USART. Для функции USBD_Read должна быть предоставлена функция обратного вызова (callback) с указателем для доступа к принятым данным.

Код поддержки запроса GET_LINE_CODING просто вызывает функцию USBD_Write для отправки хосту текущих установок USART.

Есть две возможности для сохранения текущих установок. Наиболее очевидная возможность - сохранять установки непосредственно в регистры USART, и также получать их оттуда. Этот способ экономит место в оперативной памяти. Однако из-за того, что параметры сохраняются не так, как они представлены в структуре стандарта CDC, то они должны составляться заново в нужном формате для каждого запроса GET_LINE_CODING. Другой вариант - для упрощения доступа сохранять принятые значения в выделенном экземпляре структуры (переменная в памяти), соответствующей драйверу класса.

4.5.4 SetControlLineState

4.5.4.1 Назначение

Этот запрос отправляется хостом для оповещения о двух изменениях состояния. Первый бит (D0) поля wValue в запросе показывает, подключен или нет терминал к виртуальному COM-порту. Бит D1 показывает, должен ли USART разрешить/запретить сигнал несущей для начала/завершения приема и передачи данных.

На практике конвертер USB-to-serial должен работать только тогда, когда эти два бита установлены ы 1. Иначе, он не должен передавать или принимать данные.

4.5.4.2 Реализация

Поскольку запрос SET_CONTROL_LINE_STATE не имеет полезной нагрузки (data payload), устройство должно просто подтвердить этот запрос оправкой ZLP (zero-length packet, пакет нулевой длины), используя функцию USBD_Write с нулевой длиной данных.

Перед этим поле wValue должно быть проанализировано для получения нового состояния линии управления. Одиночная переменная типа boolean может использоваться для отслеживания состояния соединения. Если оба бита, и D0, и D1, установлены в 1,  то конвертер должен работать нормально, т. е. должен пересылать данные между USART и хостом USB. Иначе конвертер должен перестать работать.

4.5.5 SendBreak

4.5.5.1 Назначение

Запрос SendBreak используется для инструктирования устройства, что нужно послать сигнал break указанной длины по линии RS-232. Этот сигнал иногда используется для привлечения внимания подключенной машины (обычно хоста на противоположном конце соединения).

4.6 Notifications (оповещения)

Оповещения отправляются устройством, когда наступает событие наподобие изменения состояния последовательной линии. В нашем примере эти оповещения передаются через выделенную отдельно конечную точку типа Interrupt IN. Для каждого оповещения должен присутствовать специальный заголовок перед полезной нагрузкой. Заголовок имеет тот же формат, что и запрос SETUP, так что может использоваться структура USBGenericRequest, заданная в AT91 USB framework.

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

4.6.1 NetworkConnection (соединение с сетью)

4.6.1.1 Назначение

Оповещение NetworkConnection используется, чтобы сказать хосту, подключен или нет коннектор на стороне RS-232. В случае использования USART нет возможности получить такую информацию, за исключением применения для этого отдельного сигнала. Таким образом, это оповещение не поддерживается конвертером USB-to-serial.

4.6.2 ResponseAvailable (доступен ответ)

4.6.2.1 Назначение

Это оповещение позволяет устройству сообщить хосту, что имеется ответ. Однако, как это уже было указано ранее, запрос GetEncapsulatedResponse не относится к нашему примеру (см. секцию 4.5.1.1), так что это оповещение не имеет смысла (и не поддерживается).

4.6.3 SerialState (состояние последовательной линии)

4.6.3.1 Назначение

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

• Buffer overrun (переполнение буфера)
• Parity error (ошибка четности)
• Framing error (ошибка фрейма)
• Ring signal detection mechanism state (состояние механизма детектирования сигнала вызова)
• Break detection mechanism state (состояние механизма детектирования сигнала Break)
• Transmission carrier state (состояние несущей передачи)
• Receiver carrier detection mechanism state (состояние механизма детектирования несущей приема)

Некоторые из этих значений относятся только к модему - ring signal detection, transmission carrier state и receiver carrier detection.

4.6.3.2 Реализация

Это оповещение может быть напрямую привязано к состоянию регистра статуса USART. И действительно, он содержит всю необходимую информацию. Для оповещения о изменения статусе USART может использоваться прерывание, и в этот момент может быть отправлено хосту соответствующее оповещение.

Чтобы отправить оповещение SERIAL_STATE, устройство должно сначала передать соответствующий заголовок оповещения. Как было указано ранее, формат этого заголовка идентичен запросу SETUP, так что может использоваться экземпляр USBGenericRequest для сохранения необходимой информации. В следующем примере задан typedef для переименования USBGenericRequest в CDCNotificationHeader:

CDCNotificationHeader cdcNotificationHeader =
{
   CDC_NOTIFICATION_TYPE,
   CDC_NOTIFICATION_SERIAL_STATE,
   0,
   0,
   0
};

Информация об актуальном состоянии передается в двух байтах. Имеют значение только первые 7 бит первого байта, все другие биты могут быть установлены в 0. Устройство должно установить биты в соответствии с текущим состоянием USART, и затем отправить данные с помощью функции USBD_Write.

В зависимости от размера interrupt endpoint заголовок и данные могут быть переданы либо одним куском (размер больше 8 байт), либо раздельно. В первом случае может быть проще определить структуру CDCSerialState, которая содержит сразу и заголовок, и полезную нагрузку (данные).

Во втором случае передача может быть осуществлена двумя последовательными вызовами USBD_Write (поскольку заголовок имеет длину 8 байт). Однако, что может оказаться неудобным, первая часть передачи должна завершиться, перед тем как вторая часть может начать передаваться. Это означает, что второй вызов должен либо быть сделан в функции callback (функция обратного вызова), которая будет вызвана по завершении первой передачи, или в цикле проверки, что принят код возврата USB_STATUS_SUCCESS (он показывает, что конечная точка больше не заблокирована).

4.7 Main Application (основная программа)

Работа функции main приложения состоит в том, чтобы осуществить мост (bridge) между USART и USB. Это означает, что данные, прочитанные от USART, должны быть переданы в USB, и наоборот.

4.7.1 USB Operation (работа USB)

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

Похожим образом может быть вызвана функция USBD_Write, как только имеются данные для передачи, также без блокирования потока выполнения программы. Однако нельзя осуществлять сразу две операции записи в одно и то же время, так что программа должна проверять - завершена ли предыдущая передача. Это можно сделать путем анализа кода возврата метода USBD_Write. Если вернулось значение USB_STATUS_LOCKED, то пока еще активна предыдущая операция передачи. Устройство должно иметь буфер для хранения данных, полученных от USART, пока конечная точка не освободится снова.

4.7.2 USART Operation (работа USART)

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

Более мощный метод доступен для чипов AT91SAM, путем использования встроенного контроллера прямого доступа к памяти (DMA Controller, PDC). Аппаратура PDC заботится о перемещении данных между процессором, памятью и периферией, что освобождает ядро микроконтроллера для выполнения других задач.

Поскольку цель этого application note только компонент USB, использование USART здесь не описано.

4.8 Пример использования программы

4.8.1 Файловая архитектура

В примере программы, предоставленной для этого application note, код драйвера делится на три файла:

• at91lib\usb\common\cdc: папка, содержащая все определения и методы стандартного CDC.
   • CDCAbstractControlManagementDescriptor.h: хедер для определений CDC ACM.
   • CDCCallManagementDescriptor.h: хедер для определений CDC Call Management (управление вызовами CDC).
   • CDCCommunicationInterfaceDescriptor.h: хедер для определений Communication Interface.
   • CDCDataInterfaceDescriptor.h: хедер для определений Data Interface.
   • CDCDeviceDescriptor.h: определения для дескриптора устройства CDC.
   • CDCGenericDescriptor.h: определения для дескрипторов CDC.
   • CDCGenericRequest.h: определения, используемые для поддержки запросов CDC.
   • CDCHeaderDescriptor.h: определения для дескриптора заголовка CDC.
   • CDCUnionDescriptor.h: определения для дескриптора объединения CDC.
   • CDCLineCoding.h: определения для запросов кодирования линии CDC.
   • CDCLineCoding.c: методы для поддержки запросов кодирования линии CDC.
   • CDCSetControlLineStateRequest.h: определения для запроса CDC SetControlLineState.
   • CDCSetControlLineStateRequest.c: методы для запроса CDC SetControlLineState.
• at91lib\usb\device\cdc-serial: папка со всеми файлами, используемыми для драйвера CDC serial.
   • CDCSerialDriver.h: хедер для определений драйвера конвертера USB-to-serial.
   • CDCSerialDriver.c: исходный код для драйвера конвертера USB-to-serial.
   • CDCSerialDriverDescriptors.h: хедер для определений дескрипторов драйвера конвертера USB-to-serial.
   • CDCSerialDriverDescriptors.c: исходный код дескрипторов драйвера конвертера USB-to-serial.
• usb-device-cdc-serial-project: папка для кода основного алгоритма приложения (main application code).
   • main.c: исходный код главного приложения.
   
Пример программного обеспечения поставляется вместе с Makefile, применяемым для сборки. Для сборки нужна утилита GNU make, которая доступна на www.GNU.org. Для получения информации по параметрам и опциям Makefile см. AT91 USB Device Framework application note.

Чтобы собрать пример конвертера USB-to-serial просто введите "make" в командной строке директории usb-device-cdcserial-project, при этом в командной строке можно добавить два параметра: CHIP= и BOARD=, значение по умолчанию для этих параметров “at91sam7s256” и “at91sam7s-ek”, например:

make CHIP=at91sam7se512 BOARD=at91sam7se-ek

В этом случае результирующий двоичный код будет называться usb-device-cdc-serial-project-at91sam7se-ekat91sam7se512-flash.bin, и он будет находится в папке usb-device-cdc-serial-project/bin.

4.9 Использование Generic Host Driver (стандартный драйвер хоста)

И Microsoft® Windows, и Linux поставляются со встроенным драйвером для использования с USB-устройством конвертера USB-to-serial. В этой секции описываются шаги, необходимые для использования этого драйвера.

4.9.1 Windows

В операционной системе Microsoft Windows стандартный USB serial driver называется usbser.sys, и он является частью стандартного набора драйверов. Этот драйвер стал доступен начиная с Windows 98SE. Однако, в отличие от других стандартных драйверов наподобие Mass Storage Devices (MSD), драйвер usbser.sys не загружается автоматически, когда устройство CDC подключено в первый раз.

4.9.1.1 Writing a Windows Driver File (написание информационного файла драйвера для Windows)

Чтобы Windows корректно распознало устройство, необходимо написать для него .inf файл. Windows Driver Development Kit (DDK) содержит информацию по этой теме. Для нашего примера драйвера поставляется базовый драйвер, который называется 6119.inf, и этот файл рассматривается здесь подробно.

Первая секция файла .inf должна быть секцией [Version]. Она содержит информацию о версии драйвера, провайдере, дате релиза и т. п.

[Version]
Signature="$Chicago$"
Class=Ports
ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
Provider=%ATMEL%
DriverVer=09/12/2006,1.1.1.1

Атрибут Signature является обязательным и может быть либо “$Windows 95$”, “$Windows NT$” или “$Chicago$”, в зависимости от той версии (версий) операционной системы, которую драйвер поддерживает. Значение “$Chicago$” используется для случая, когда поддерживаются все версии операционных систем Windows. Поскольку в этом примере конвертер USB-to-serial используется как virtual COM port, то атрибут Class должен быть "Ports". Значение ClassGuid зависит от класса, который использует устройство. Значение Provider показывает, что строковый дескриптор для поставщика драйвера (провайдера) будет задан в будущем, помеченный тэгом ATMEL. И в заключении последний тег показывает версию драйвера и дату релиза. Для номера версии каждая цифра является необязательной, за исключением первой цифры, однако если этот тег присутствует, то он не должен быть пустым.

Следующими идут две секции, [SourceDisksNames] и [SourceDisksFiles]. Они используются для указания необходимых дисков для инсталляции, и размещения каждого необходимого файла на этих дисках.

[SourceDisksNames]
1="Windows Install CD"

[SourceDisksFiles]
usbser.sys=1

Первая секция дает список имен исходных дисков, на которых пользователь может найти недостающие для установки драйвера файлы. Поскольку драйверу нужен файл usbser.sys, который есть на инсталляционном Windows CD, то этот диск здесь и указан. Идентификатор диска должен быть уникальным, в виде ненулевой цифры (ID = 1). Вторая секция показывает, на каком диске можно найти каждый файл. В нашем случае, usbser.sys может быть найден на диске 1 (который "Windows Install CD"). Может быть указан полный путь (что необязательно) до файла на CD.

Теперь файл драйвера должен указать путь, куда нужно записать скопированные файлы, используя секцию [DestinationDirs].

[DestinationDirs]
DefaultDestDir=12

Целевая директория должна быть идентифицирована по своему ID, который определен для системы. ID для директории drivers равен 12.

Секция [Manufacturer] перечисляет возможных производителей для всех устройств, поддерживаемых этим драйвером. В нашем случае поддерживается только устройство производства ATMEL, так что тут будет только это значение.

[Manufacturer]
%ATMEL%=AtmelMfg

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

Каждая секция Models должна перечислить hardware ID каждого из поддерживаемых устройств. Для устройств USB hardware ID составляется из Vendor ID, Product ID и (что необязательно) из Device Release Number (номер релиза устройства). Эти значения вычитываются из дескриптора устройства, который предоставляется хосту на фазе энумерации устройства USB.

[AtmelMfg]
%USBtoSerialConverter%=USBtoSer.Install,USB\VID_03EB&PID_6119

Имя атрибута снова представлено в виде строкового тэга, который используется для описания устройства. Значение состоит из имени секции инсталляции устройства (USBtoSer.Install) и из hardware ID. Значение hardware ID то же самое, как указано в секции 4.4.1ю

Теперь файл .inf должен детализировать секцию инсталляции для каждого ранее перечисленного устройства. В нашем примере будет только одна секция инсталляции, которая носит имя USBtoSer.Install:

[USBtoSer.Install]
CopyFiles=USBtoSer.CopyFiles
AddReg=USBtoSer.AddReg

[USBtoSer.CopyFiles]
usbser.sys,,,0x00000002

[USBtoSer.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,usbser.sys

[USBtoSer.Install.Services]
AddService=usbser,0x00000002,USBtoSer.AddService

[USBtoSer.AddService]
DisplayName=%USBSer%
ServiceType=1r
StartType=3
ServiceBinary=%12%\usbser.sys

Секция инсталляции состоит из 5 частей. В первой секции указаны имена двух других секций: одна списка файлов для копирования, другая для ключей, которые нужно добавить в реестр Windows. Для копирования здесь имеется только один файл, usbser.sys; флаг (0x00000002) используется, чтобы указать, что пользователь не может пропустить его копирование. Ключи реестра нужны нужны для установки драйвера в старых версиях Windows (как например Windows 98). Для новых версий нужны регистры [USBtoSer.Install.Services] для сервисов ядра; каждый сервис реально перечислены в своей собственной секции.

И наконец, последняя секция [Strings], задает все строковые константы используемые в этом файле:

[Strings]
ATMEL="ATMEL Corp."
USBtoSerialConverter="AT91 USB to Serial Converter"
USBSer="USB Serial Driver"

4.9.1.2 Использование драйвера.

Когда новое устройство подключается в первый раз, Windows ищет подходящий специфический драйвер для его использования. И если не находит, то выдает пользователю запрос - что делать? Это как раз случай конвертера USB-to-serial, поскольку он не использует generic драйвер. Для установки специального драйвера, указанного в предыдущей секции, для Windows нужно указать, где его искать. Это можно сделать путем выбора второй опции, “Install from a list or specific location”, когда запустится мастер установки драйвера. Он запросит директорию, где находится драйвер. Укажите ему папку, где находится файл 6119.inf. После этого, Windows распознает драйвер “AT91 USB to Serial Converter” и отобразит его в списке Диспетчера Устройств.

Во время инсталляции мастер также запросит размещение файла usbser.sys. Обычно он уже установлен в системе, и находится в папке "C:\Windows\System32\Drivers\". В противном случае, его можно найти на Windows installation CD - инсталляционном диске Windows.

Как только драйвер правильно установился, в системе появится новый COM порт, и его можно использовать, к примеру, в программе HyperTerminal.

4.9.2 Linux

Linux имеет два различных стандартных (generic) драйвера, которые подходят для нашего конвертера USB-to-serial. Первый из них - драйвер Abstract Control Model, разработанный для устройств модема, и он просто называется acm. Другой драйвер - простой драйвер "USB to serial", он называется usbserial.

Если поддержка драйвера acm скомпилирована в ядре, Linux будет автоматически загружать его. Новое терминальное устройство будет создано под именем /dev/ttyACMx.

Драйвер usbserial должен быть загружен вручную с помощью команды modprobe, с указанием vendor ID и product ID, которые используются в устройстве USB:

modprobe usbserial vendor=0x03EB product=0x6119

Как только драйвер загружен, появится новая терминальная запись, которая будет называться /dev/ttyUSBx.

[Ссылки]

1. Примеры приема и передачи данных через виртуальный последовательный порт USB CDC (исходный код в виде проекта IAR 5.50).
2. Схема макетной платы AT91SAM7X.
3. Работа с кольцевым буфером.
4. Бесплатная программа для тестирования последовательных портов COM Port Stress Test.
5. USB консоль для управления радиолюбительскими приборами.
6. AT91SAM7X: интерфейс USB (UDP, USB Device Port)

 

Комментарии  

 
0 #1 Виктор 04.03.2013 18:37
Интересныt и полезные статьи пишите. Спасибо. У меня тут возникла такая проблема: после каждого вызова CDCDSerialDrive r_Write и CDCDSerialDrive r_Read все линии ввода-вывода сбрасываются. Вы случайно не сталкивались с такой бедой?

microsin: не сталкивался. Может быть, у Вас происходит перезагрузка из-за зависания?
Цитировать
 

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


Защитный код
Обновить

Top of Page