Создаем простейшее usb-устройство для общения со своей программой

    В продолжение темы о создании собственного USB-гаджета.
    Создание простого устройства.

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

    Самым простым вариантом передачи данных является использование класса коммуникационных устройств USB (CDC).
    При таком подключении устройство будет видно в системе как обычный виртуальный COM-порт.
    Плюсом такого подключения является отсутствие необходимости писать собственные драйвера.
    Так же радует простота приема и передачи данных: для работы с портом в Windows достаточно открыть его как текстовый файл и производить обычные операции чтения\записи.

    Железо.


    Возьмем схему с минимальной обвязкой МК.



    На этот раз нам нужно добавить только 4 контакта к USB и одну кнопку (кнопка нужна только для бутлоадера: куда проще нажать ее и заменить прошивку в устройстве по USB, нежели переставлять чип в программатор).



    Не сильно стараясь сделать красиво, разводка может выглядеть так:



    Но при желании часто экспериментировать с подключаемыми компонентами лучше сразу развести каждую ногу МК сделав аналог ардуино — Jaluino.

    Прошивка


    Начнем с минимума:
    include 18f2455 -- библиотека для используемого МК
    --
    enable_digital_io() -- переключение всех входов на цифровой режим
    --
    alias Button is pin_B7 -- раз уж у нас подключена кнопка, объявим ее
    pin_B7_direction = input -- кнопка у нас работает на вход
    --
    -- одна строчка - и у нас есть все необходимое для работы с USB CDC
    include usb_serial -- бибилотека для работы с usb
    --
    usb_serial_init() -- --инициализируем USB CDC
    forever loop -- основной цикл, выполняется постоянно
    usb_serial_flush() -- обновление usb. Данная процедура выполняет все необходимые
    -- действия для поддержания соединения с ПК
    end loop


    Скомпилировав данный код, записав полученный HEX файл в МК при помощи бутлоадера и запустив устройство можно будет наблюдать как в системе опрделится новое устройство: Виртуальный сom-порт.



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

    Для чтения принятого байта существует функция usb_serial_read(byte):boolean. При наличии полученного байта она заносит его в указанную переменную и возвращает true, иначе возвращает false.

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

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

    include 18f2455
    --
    enable_digital_io()
    --
    alias Button is pin_B7
    pin_B7_direction
    = input
    --
    --
    include usb_serial
    --
    usb_serial_init()
    var byte ch -- объявляем переменную
    forever loop -- основной цикл
    usb_serial_flush()
    if ( usb_serial_read( ch ) ) then -- если байт получен, он будет записан в ch
    usb_serial_data = ch -- отправляем полученный байт обратно
    end if
    end loop


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

    Пока у нас нет своего, используем готовый терминал: я использовал программу RealTerm.
    Открываем порт с нужным номером и отправляем данные.


    И нам в ответ приходит то, что мы отправили. Значит, все работает как надо.

    Софт


    Итак, наш микроконтроллер умеет принимать байты и тут же отправлять их обратно. Теперь напишем свой софт для общения с ним (я буду использовать Delphi).

    Создаем новый проект, раскидываем по форме необходимые компоненты:
    SpinEdit1 — для указания номера порта
    Button1 — для установки соединения
    Button2 — для разрыва соединения
    SpinEdit2 — для ввода байта в десятичном виде
    Button3 — для отправки байта
    Memo1 — для вывода принятой информации.

    Как уже было сказано выше, с com-портом нужно работать так же, как и с обычным текстовым файлом: используя функции CreateFile, WriteFile и ReadFile.

    Дабы не вдаваться в подробности, возьмем готовую библиотеку для работы с com-портом: ComPort.

    Вешаем на каждую кнопку необходимую задачу и получаем конечный код:

    unit Unit1;

    interface

    uses
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
     Dialogs, StdCtrls, Spin,ComPort;

    type
     TForm1 = class(TForm)
      SpinEdit1: TSpinEdit;
      Button1: TButton;
      Button2: TButton;
      SpinEdit2: TSpinEdit;
      Button3: TButton;
      Memo1: TMemo;
      procedure OnRead(Sender: TObject; ReadBytes: array of Byte);
      procedure Button1Click(Sender: TObject);
      procedure Button2Click(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
      procedure Button3Click(Sender: TObject);
     private
      { Private declarations }
      Port: TComPort;
     public
      { Public declarations }
     end;

    var
     Form1: TForm1;
     num: integer;
    implementation

    {$R *.dfm}

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    Port := TComPort.Create(SpinEdit1.Value, br115200); //создаем соединение
    Port.OnRead := OnRead;               //создаем поток чтения принятых данных
    Button2.Enabled := true;              //активируем кнопку закрытия соединения
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
    Port.Free;        //закрываем соединение
    Button2.Enabled := false; //отключаем кнопку
    end;

    procedure TForm1.Button3Click(Sender: TObject);
    begin
    if Button2.Enabled then Port.Write([SpinEdit2.Value]);
    end;

    procedure TForm1.FormDestroy(Sender: TObject);
    begin
    if Button2.Enabled then
    Port.Free;
    end;

    procedure TForm1.OnRead(Sender: TObject; ReadBytes: array of Byte);
    var
    i:integer;
    begin
      for i := Low(ReadBytes) to High(ReadBytes) do    //проходим по массиву принятых байт
      begin
        Memo1.Text := Memo1.Text + '.'+InttoHex(ReadBytes[i],2); //добавляем его HEX значение в окно
        inc(num); //считаем колв-о принятых байт
      end;
    if num > 10 then begin
     Memo1.Lines.Add(''); //переносим строку
     num := 0;
    end;
    end;

    end.


    Запускаем, устанавливаем соединение, отправляем байты:



    Вот и готов наш самый простой терминал для работы с самым простым usb-устройством.

    Как видно, чтение и запись происходит динамическими массивами байт.

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

    include 18f2455
    --
    enable_digital_io()
    --
    alias Button is pin_B7
    pin_B7_direction
    = input
    --
    --
    include usb_serial
    --
    usb_serial_init()
    var byte ch
    var byte i -- объявляем вторую переменную
    forever loop -- основной цикл
    usb_serial_flush()
    if ( usb_serial_read( ch ) ) then -- если байт получен выполняем необходимые действия
    case ch of -- перебираем номер байта
    0: usb_serial_data = 0xff
    1: usb_serial_data = Button -- отправка состояния кнопки
    OTHERWISE block -- если получено что-то иное
    for 16 using i loop -- отправляем 10 байт с данными
    usb_serial_data = ch+i -- от ch до ch+15
    end loop
    end block
    end case
    end if
    end loop


    Дополнительные возможности


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

    Упрощение отправки данных


    Отправлять информацию по одному байту — не всегда удобно. Очень часто может пригодиться библиотека print. Она содержит процедуры по отправке данных всевозможной длины всевозможными форматами: byte,hex,dec,bin,boolean что может упростить вывод данных в программе.

    >include print
    ...
    var dword data
    print_dword_hex
    (usb_serial_data, data)


    Название всех команд можно посмотреть в файле библиотеки.

    Ожидание подключения к ПК


    Если перед стартом основного цикла микроконтроллера необходимо предварительно установить соединение с ПК, то можно дописать перед ним строчки

    while ( usb_cdc_line_status() == 0x00 ) loop
    end loop


    Привязываем к устройству номер порта


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

    const byte USB_STRING3[24] =
    {
    24, -- длина массива
    0x03, -- bDescriptorType
    "0", 0x00,
    "1", 0x00,
    "2", 0x00,
    "3", 0x00,
    "4", 0x00,
    "5", 0x00,
    "6", 0x00,
    "7", 0x00,
    "8", 0x00,
    "9", 0x00,
    "X", 0x00
    }


    Меняем имя устройства на свое


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

    const byte USB_STRING2[28] =
    {
    28, --
    0x03, -- bDescriptorType
    "D", 0x00,
    "e", 0x00,
    "m", 0x00,
    "o", 0x00,
    " ", 0x00,
    "B", 0x00,
    "o", 0x00,
    "a", 0x00,
    "r", 0x00,
    "d", 0x00,
    " ", 0x00,
    "=", 0x00,
    ")", 0x00
    }




    Но увы, после установки драйверов устройство поменяет имя на указанное в .inf файле, потому поменяем имя и там
    [Strings]
    DESCRIPTION=«Demo CDC»




    Организуем автоподключение устройства


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

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

    Прошивка:
    В прошивке необходимо объявить две переменные до подключения библиотеки USB
    const word USB_SERIAL_PRODUCT_ID = 0xFF10
    const word USB_SERIAL_VENDOR_ID = 0xFF10

    Вместо FF10 можно вставить любые два слова (2 байта). Конечный результат содержится в прилагаемом архиве.

    Драйвера:
    Так как драйвера не предназначены для нашей комбинации VID и PID, допишем наши значения в .inf файл вручную:

    [DeviceList]
    %DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10

    [DeviceList.NTamd64]
    %DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10


    Софт:
    Для отлова событий подключения\отключения устройства подключим библиотеку ComponentUSB. Не считаю нужным пояснять каждую строчку: все изменения можно увидеть в прилагаемом проекте.

    Результат


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



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

    И напоследок: советую заглянуть в исходный код лампы настроения. Там можно найти довольно-таки хороший вариант обработки принимаемых данных для организации удобного протокола обмена.

    P.S.
    Файлы проекта.
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 35
    • –2
      Спасибо, то что надо.
      • 0
        Весьма полезный материал, спасибо!
        • +4
          Признаюсь — половину не понял, но заинтересовал результат и удивительно короткий путь для его получения. Думал все в разы сложнее. Спасибо, думаю Ваш пост стал моим стимулом покопать в сторону программирования МК поглубже, научиться новому!)
          • –1
            То что нужно, первая + эта статья. Спасибо!
            • 0
              Повозился с драйверами нестандартных устройств еще под Досом.
              Даже тот же Паскаль пользовал, только asm писал в процедурах.
              Там было намного сложнее с отладкой (голова на 99% инструмент).
              Но под виндой уже «не потянул» — пару лет не было нормальной инфы по встраиванию драйвера в список оборудования. Пришлось переходить на готовые аналоги — уже как черный ящик.
              Вижу, что ничего страшного. Спасибо, лампа настроения работает :)
              • 0
                А не могли бы для ну совсем валенков написать как собрать на коленке программатор и что делать, если «всё собрал по схеме, но ничего не работает».
                • 0
                  У вас не заработала ни одна из двух схем простейших программаторов? Или просто заранее хочется знать причину возможной неудачи?
                  • 0
                    В своё время делал по «простейшим схемам» программатор для ATTiny, так ничего и не заработало. Поэтому заранее хочу знать обо всех подводных камнях.
                    • 0
                      Хочу научиться программировать. Расскажите о всех подводных камнях. =)
                      • 0
                        Простейший программатор для ATTiny насколько я помню — это просто выводы с LPT порта + резисторы. «Схема» которую очень сложно заставить не работать ) Вы делали что-то подобное?
                        • 0
                          Я уже точно не помню, но наверняка что-то похожее. Откуда мне знать, почему она не заработала.
                        • 0
                          Да все возможные ошибки не перечислить. Возможно все: от непропая, до несовместимости компа с софтом.

                          Собрать схему программатора, вставить МК, подключить к ПК.
                          Запустить софт, указать тип программатора, нажать кнопку Hardware test. МК должен определиться корректно.
                          Открыть .hex файл бутлоадера, нажать Program all, затем Verify All.
                          Если ошибок не обнаружено — значит все получилось.

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

                          Попробуйте. Ну, а если вам жалко времени — купите готовый заведомо рабочий программатор. К примеру, PicKit2.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • +2
                        Есть, только не так прозрачно.
                        Открытие порта и назначение параметров: (в argv[1] — имя порта /dev/ttyUSB1 например)
                        #include <fcntl.h>
                        #include <termios.h>
                        #include <linux/types.h>
                        #include <sys/signal.h>
                        int fd;
                        bool wait_flag=true;
                        struct sigaction saio;
                        ...
                        fd=open(argv[1],O_RDWR | O_NOCTTY | O_EXCL);
                        saio.sa_handler = signal_handler_IO;

                        sigemptyset(&saio.sa_mask); //saio.sa_mask = 0;
                        saio.sa_flags = 0;
                        saio.sa_restorer = NULL;
                        sigaction(SIGIO,&saio,NULL);

                        fcntl(fd, F_SETOWN, getpid());
                        fcntl(fd, F_SETFL, FASYNC);

                        termios options;
                        tcgetattr(fd, &options);
                        cfsetispeed(&options,B19200);
                        cfsetospeed(&options,B19200);
                        options.c_iflag &= ~IGNPAR;
                        options.c_iflag &= ~CSTOPB;
                        options.c_oflag = 0;
                        options.c_lflag = 0; //ICANON;
                        options.c_cc[VMIN] = 0;
                        options.c_cc[VTIME] = 1;
                        options.c_cflag |= (CS8 | CLOCAL | CREAD | CRTSCTS);

                        Все, порт открыт.
                        Дальше через write писать в fd.

                        signal_handler_IO — функция коллбека типа:
                        void signal_handler_IO (int status)
                        {
                        int rnum=read(fd,buf,100); // читать 100 символов в буфер из порта, читать нужно с избытком, в rnum будет реальное количество прочитанного
                        ...
                        wait_flag = false;
                        }

                        Она вызывается всегда когда приходит новый текст.
                        wait_flag — флаг, которым можно контролировать пришел ответ от девайса или нет.
                        После записи write нужно делать ожидание ответа.
                        while (wait_flag==true);
                        wait_flag=true;

                        Конечно можно прикрутить таймауты.

                        Может, конечно, есть и библиотеки, упрощающие задачу.
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • 0
                            Так это уже код прошивки самого контроллера. Это уже не gcc насколько я понимаю, тут нужен свой компилятор. С этим не сталкивался, я писал работу с портом — только софтверную часть.
                            Думаю, найдется какой-то компилятор для линукса.
                            • +2
                              Посмотрел, JAL, который использует автор статьи, есть и для линукса.
                              Цитата с сайта:
                              Lastest release files (JALv2 2.4n):

                              README.txt
                              archive/README.txt
                              Binaries (win32 and linux with chipdef files)
                              archive/jalv24n.zip
                              Sources
                              archive/jalv24nsrc.zip
                          • 0
                            Qt не имеет модуля для работы с USB устройствами. Можете посмотреть libusb.
                          • –1
                            вот бы теперь тоже самое, только для Cortex M3
                            • 0
                              Перейдёте на другой МК — снова будете спрашивать где взять программу? Давайте уж как-нибудь сами, ладно.
                              • 0
                                Все-таки нашел время осмотреться…
                                А ведь в этом (изучение Cortex M0/M3) есть смысл.

                                Закажу себе для начала немного, может понравится лучше пиков (а то привыкну еще, потом не оттащить будет =) ).
                              • –2
                                В избранное.
                                • +2
                                  А без serial нельзя обойтись?
                                  Например, чтобы вообще не ставить драйвер, сделать обмен с устройством через HID.
                                  • +1
                                    Можно, но будет заметно сложнее. Пример для AVR — 1010.co.uk/avrhid.html
                                    • 0
                                      www.lvr.com/hidpage.htm тут для разных процессоров (в том числе и пиков).
                                      Сам работал с вот этим примером: www.obdev.at/products/vusb/automator.html
                                      Всё довольно просто. Самое сложное в HID — сгенерить правильный дескриптор. А сама работа с USB — может быть как аппаратной, так и с программной эмуляцией.
                                      • +1
                                        На аппаратном USB не сильно сложнее будет, а по вашей ссылке опять же софтверный USB, где основная сложность как раз в части firmware.
                                      • 0
                                        Можно обойтись и без CDC. И даже без HID.
                                        Если захотеть, можно сделать очень многое.

                                        Но я решил для начала рассмотреть самый простой в реализации, а не удоный для пользователя вариант.
                                      • 0
                                        Для большей устойчивости связи все-таки рекомендуется использовать такое подключение к USB:
                                        • 0
                                          Думается эта схема для контроллеров, где USB реализуется софтверно (VUSB для AVR напрмиер), а значит и порты ввода/вывода работают на логических уровнях 5V. Тогда правда стоит или занижать питание (если контроллер позволяет) или ставить стабилитроны как у вас в схеме, т.к напряжение на дифф. паре около 3V.
                                          В этом МК явно есть аппаратный USB судя по подписям ножек, а значит и напряжения выдаются соответствующие.
                                          • 0
                                            Прежде всего необходимо заглянуть в даташит на используемый МК. Повторю линк: PIC18F2455/2550/4455/4550
                                            Data Sheet
                                            .
                                            Более 400 страниц полезной информации, там можно найти всю необходимую информацию до последних мелочей.

                                            И про подключение USB там есть:

                                            • +1
                                              Не дописал…

                                              Сразу видно, что в МК содержится собственный регулятор 3.3в, потому не требуется ни внешнего, ни мороки с диодами и резисторами.

                                              Так же на схеме видно, что можно использовать внешний регулятор, но нужно обязательно отключить внутренний. Тем самым (я предполагаю) освободится 14 нога МК, на которой сейчас висит конденсатор.
                                          • 0
                                            Как я понимаю в данном ПИКе просто встроено что-то типа FTDI232? Или USB полностью софтверный?
                                            • 0
                                              Данный пик имеет аппаратный usb.
                                            • 0
                                              Недавно собирался старый комп собрать ради ком порта. Теперь буду с USB работать)
                                              Спасибо!!!
                                              • 0
                                                Спасибо большое. Мне очень помог материал.
                                                Разрабатываю цифро-анаолговое устройство.
                                                Планирую использовать USB для управления и получения тестовых данных.

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