Главная arrow Программирование arrow ARM arrow ARM7: техника использования виртуального последовательного порта USB CDC Monday, September 25 2017  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

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

Поделиться:

ARM7: техника использования виртуального последовательного порта USB CDC Версия для печати
Написал microsin   
01.07.2011

Пример 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, иначе, если сделать наоборот, то это может стать причиной трудноуловимой ошибки - обработчик прерывания конечной точки может сработать до сброса флажка TXCDCdone, и новая передача больше никогда не начнется. На диаграмме показан алгоритм передачи данных. Код между метками НАЧАЛО и конец должен вызываться в главном цикле 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)
{
  CDCDSerialDriver_Read(usbCDCBuffer,
                         CDCDATABUFFERSIZE,
                         (TransferCallback) UsbCDCDataReceived,
                         0);
  NeedInitCDCDSerialDriver_Read = false;
}

Принимаемые данные могут быть прочитаны из кольцевого буфера RXCDC в любой момент по индексу outRXCDC, когда это удобно (подробнее про технику работы с кольцевым буфером см. в [3]).

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

COM-Port-Stress-Test01.PNG

[Ссылки]

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

Последнее обновление ( 10.09.2012 )
 

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

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

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

< Пред.   След. >

Top of Page
 
microsin © 2017