We create the simplest usb device to communicate with our program

    Continuing the theme of creating your own USB-gadget.
    Create a simple device.

    Since the device is planned to be connected to a PC, it means that most likely data transfer between the device and the PC is required.
    Let's start writing firmware and software, having established a connection between them.

    The easiest way to transfer data is to use the class of USB communication devices (CDC).
    With this connection, the device will be visible in the system as a regular virtual COM port.
    The advantage of this connection is the lack of the need to write your own drivers.
    The simplicity of receiving and transmitting data is also pleasing: to work with a port in Windows, it is enough to open it as a text file and perform normal read / write operations.


    Take a circuit with a minimal MK strapping. This time we need to add only 4 pins to USB and one button (the button is needed only for the bootloader: it is much easier to press it and replace the firmware in the device via USB than to rearrange the chip in the programmer). Not trying hard to make it beautiful, the wiring may look like this: But if you want to often experiment with plug-in components, it is better to immediately part each MK leg by making an analogue of the arduino - Jaluino .


    Let's start with a minimum:
    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

    Having compiled this code, writing the received HEX file to MK using a bootloader and starting the device, you can observe how a new device is defined in the system: Virtual COM port.

    Now that the device is already working, we will teach it to communicate.

    To read the received byte there is a function usb_serial_read ( byte ) : boolean. If there is a byte received, it enters it into the specified variable and returns true , otherwise returns false .

    There is a usb_serial_data procedure to send a byte . It is disguised as a variable, therefore to send a byte it is enough to assign it the value of the byte to be sent.

    We will declare a variable of byte size before the main loop, in the main loop we will check for the received bytes, and if there are any, send them back.

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

    We compile, hold down the button, distort the power, launching the bootloader, change the firmware, launch.
    The device was again defined in the system, now we need software in order to test the operation of the device.

    While we don’t have ours, we use a ready-made terminal: I used the RealTerm program.
    We open the port with the desired number and send the data.

    And we receive what we sent. So, everything works as it should.


    So, our microcontroller can receive bytes and send them back immediately. Now we will write our software to communicate with it (I will use Delphi).

    We create a new project,
    scatter the necessary components in the form: SpinEdit1 - to indicate the port number of
    Button1 - to establish a connection
    Button2 - to break the connection
    SpinEdit2 - to enter the byte in decimal form
    Button3 - to send the byte
    Memo1 - to display the received information.

    As mentioned above, you need to work with the com-port in the same way as with a regular text file: using the CreateFile, WriteFile and ReadFile functions.

    In order not to go into details, let's take a ready-made library for working with com-port: ComPort.

    We hang on each button the necessary task and get the final code:

    unit Unit1;


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

     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 declarations }
      Port: TComPort;
      { Public declarations }

     Form1: TForm1;
     num: integer;

    {$R *.dfm}

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

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

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

    procedure TForm1.FormDestroy(Sender: TObject);
    if Button2.Enabled then

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


    We start, establish a connection, send bytes:

    So our simplest terminal is ready to work with the simplest usb device.

    As you can see, reading and writing occurs by dynamic arrays of bytes.

    Processing the information received, it is possible to compile the necessary exchange protocol suitable for the current task.

    include 18f2455
    alias Button is pin_B7
    pin_B7_direction = input
    include usb_serial
    var byte ch
    var byte i -- объявляем вторую переменную
    forever loop -- основной цикл
    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

    Additional features

    If we stop at this, we get an ordinary article with a detailed description of an example of using the library, of which there are enough on the open spaces of the network. Therefore, I will add a little more in-depth information.

    Simplification of data sending

    Sending information by one byte is not always convenient. Very often the print library can come in handy . It contains procedures for sending data of various lengths in various formats: byte, hex, dec, bin, boolean which can simplify the output of data in the program.

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

    The name of all commands can be found in the library file.

    Waiting for PC connection

    If before starting the main cycle of the microcontroller you must first establish a connection with a PC, then you can add lines in front of it

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

    We bind a port number to the device

    If you leave everything as it is, the system with each new connection will allocate the first free port number. And this means that you will always have to follow him.
    In order to prevent this from happening, it is necessary for the device to assign a unique value to the serial number before connecting the usb library: The
    number can be of any length and contain various characters.

    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

    Change the device name to your

    You can change the device name visible in the system before installing the drivers by declaring an array with the name, as well as the serial number, this must be done before connecting the USB library.

    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

    But alas, after installing the drivers, the device will change the name to the one specified in the .inf file, therefore we will change the name there too

    We organize the automatic connection of the device

    Alas, there are no direct ways to accomplish this task, so you have to contrive.

    First of all, you need to assign a unique value to your device manufacturer and product, so that it is easy to identify it among hundreds of other standard CDC firmware.
    VID and PID are issued for denyuzhku, so we go along the path of the Chinese: quietly take yourself obviously free values.

    Two variables must be declared in the firmware before connecting the USB library
    const word USB_SERIAL_PRODUCT_ID = 0xFF10
    const word USB_SERIAL_VENDOR_ID = 0xFF10

    Instead of FF10, you can insert any two words (2 bytes). The final result is contained in the attached archive.

    Since the drivers are not designed for our combination of VID and PID, we will add our values ​​to the .inf file manually:

    % DESCRIPTION% = DriverInstall, USB \ VID_FF10 & PID_FF10

    % DESCRIPTION% = DriverInstall, USB \ VID_FF10 & PID_FF10

    To catch events of connecting / disconnecting a device, connect the ComponentUSB library. I do not consider it necessary to explain each line: all changes can be seen in the attached draft.


    It is difficult to see in the screenshot, but the send button is active only when a connected device is available, and every 50ms the program asks for the state of the button (which, however, is wrong, because pressing the button should be processed on the MK).

    As you can see, organizing the exchange of data between MK and PC via USB is not the most difficult task. The resulting connection can be used not only for final purposes: it is also suitable for debugging a program. After all, sending the results of calculations, the current state of registers and variables to a computer is much more obvious than blinking a pair of LEDs in Morse code.

    And finally: I advise you to look into the source code of the mood lamp. There you can find a pretty good option for processing received data to organize a convenient exchange protocol.

    project files .

    Also popular now: