Небольшой пример с LUFA + HIDAPI.

AVR
1. Введение
LUFA – библиотека с открытым исходным кодом (лицензия MIT), позволяющая легко реализовать USB-устройства на основе микроконтроллеров AVR с аппаратным USB. LUFA разработана для использования только с компилятором avr-gcc (Хотя уже портировали на LPC — раз и два)

2. Firmware
Рассмотрим, как реализовать с помощью данной библиотеки устройство многострадального HID-класса. Для примера я использую LED-матрицу 16х16 на четырёх 74HC595, подключённую по SPI к платке с МК AT90USB162.

Для начала скачиваем последнюю версию библиотеки отсюда.
Создадим папку для нового проекта, скопируем туда папку с именем «LUFA» из архива.
Для простоты воспользуемся примером из папки Demos. Заходим в папку Demos\Device и видим тут ещё три папки: ClassDriver, Incomplete и LowLevel. В первой и в последней есть нужный нам пример GenericHID. Разница между ними в том, что примеры из папки ClassDriver используют библиотечные функции для упрощения использования стандартных классов USB, а в примерах из папки LowLevel написаны для этого свои функции. Хотя документация LUFA настоятельно рекомендует новичкам воспользоваться примерами из папки ClassDriver, мы будем использовать пример из LowLevel и вот почему: согласно спецификации, HID устройство обязательно должно иметь нулевую конечную точку (для управляющих посылок) и конечную точку типа Interrupt IN (для оправки входных репортов хосту). Опционально, устройство может иметь конечную точку типа Interrupt OUT, принимающую выходные репорты от хоста. Если устройство не имеет такой конечной точки (либо такой режим не поддерживается хостом), то выходные репорты посылаются через нулевую конечную точку. Пример из папки ClassDriver не использует конечную точку типа Interrupt OUT. Поэтому для большего интереса используем пример из папки LowLevel\GenericHID. Забираем оттуда следующие файлы:

makefile
Descriptors.c
GenericHID.c
Descriptors.h
GenericHID.h

Теперь нужно отредактировать данные примеры под нашу задачу. LED-матрица состоит из 256 светодиодов, следовательно, чтобы управлять всеми светодиодами, от хоста к устройству нужно передавать 32 байта данных. На платке с контроллером у меня установлена пользовательская кнопка, поэтому можно передавать её состояние в компьютер. Также, можно передавать ещё какую-то дополнительную информацию. Выделим для этого один байт, который устройство будет передавать хосту. Таким образом, размер выходного репорта — 32 байта, размер входного — 1 байт.
Открываем файл Descriptors.h и видим там такой дефайн:

  #define GENERIC_REPORT_SIZE       8

У нас два разных репорта, поэтому опишем их размеры таким образом:

  #define OUT_REPORT_SIZE       32
  #define IN_REPORT_SIZE        1


Переходим к файлу Descriptors.c. Подредактируем дескриптор репорта:

const USB_Descriptor_HIDReport_Datatype_t PROGMEM GenericReport[] =
{
  HID_RI_USAGE_PAGE(16, 0xFF00), /* Vendor Page 0 */
  HID_RI_USAGE(8, 0x01), /* Vendor Usage 1 */
  HID_RI_COLLECTION(8, 0x01), /* Vendor Usage 1 */
      HID_RI_USAGE(8, 0x02), /* Vendor Usage 2 */
      HID_RI_LOGICAL_MINIMUM(8, 0x00),
      HID_RI_LOGICAL_MAXIMUM(8, 0xFF),
      HID_RI_REPORT_SIZE(8, 0x08),
      HID_RI_REPORT_COUNT(8, IN_REPORT_SIZE),
      HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
      HID_RI_USAGE(8, 0x03), /* Vendor Usage 3 */
      HID_RI_LOGICAL_MINIMUM(8, 0x00),
      HID_RI_LOGICAL_MAXIMUM(8, 0xFF),
      HID_RI_REPORT_SIZE(8, 0x08),
      HID_RI_REPORT_COUNT(8, OUT_REPORT_SIZE),
      HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE),
  HID_RI_END_COLLECTION(0),
};

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

const USB_Descriptor_HIDReport_Datatype_t PROGMEM hid_report[] =
{
  0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
  0x09, 0x00,                    //   USAGE (Undefined)
  0xa1, 0x01,                    //   COLLECTION (Application)
  0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
  0x75, 0x08,                    //     REPORT_SIZE (8)
  0x95, OUT_REPORT_SIZE,         //     REPORT_COUNT(32)
  0x09, 0x00,                    //     USAGE (Undefined)
  0x92, 0x02, 0x01,              //     OUTPUT (Data,Var,Abs,Buf)
  0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
  0x75, 0x08,                    //     REPORT_SIZE (8)
  0x95, IN_REPORT_SIZE,          //     REPORT_COUNT(1)
  0x09, 0x00,                    //     USAGE (Undefined)
  0x82, 0x02, 0x01,              //     INPUT (Data,Var,Abs,Buf)
  0xc0                           //   END_COLLECTION
};


Тут же можно пробежаться по остальным дескрипторам и подправить в них что-нибудь. Например, исправить VID/PID или строки с описанием устройства и производителя :).

Теперь переходим к файлу GenericHID.h. Так исходный пример подразумевает использование отладочной платы USBKEY, то тут мы видим много совсем не нужных для нашей задачи вещей. В частности, можно удалить все макросы и инклуд #include <LUFA/Drivers/Board/LEDs.h>, а также описания функций

  void EVENT_USB_Device_Connect(void);
  void EVENT_USB_Device_Disconnect(void);
  void EVENT_USB_Device_StartOfFrame(void);

В примере они используются только для индикации состояний устройства, в нашем случае они не пригодятся.

Переходим к файлу GenericHID.c.
Начнём с функции main. Опять удаляем всё, что связано со светодиодами (LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);)

Далее функция SetupHardware.
Видим тут отключение wdt, сброс предделителя тактового генератора (на случай, если установлен фьюз CKDIV8) и инициализацию модуля библиотеки. LEDs_Init(), очевидно, нужно тоже удалить.
Теперь нужно сюда вписать инициализацию SPI и сопутствующих ножек. Для инициализации и использования SPI, можно воспользоваться драйвером библиотеки (LUFA содержит функции для инициализации и работы с SPI, TWI, ADC и USART) либо сделать это напрямую через регистры. Для примера посмотрим, как сделать это с помощью библиотеки. Для инициализации SPI используется функция SPI_Init. В качестве параметра ей передаётся байт настроек, биты которого описаны в документации. В соответствии с нужным нам режимом, инициализация будет выглядеть следующим образом:
SPI_Init(SPI_MODE_MASTER|SPI_SPEED_FCPU_DIV_2);
При такой инициализации SPI будет работать в режиме мастера, со скоростью в половину тактовой частоты, старший бит будет отправлен первым, данные будут выдаваться по нарастающему фронту.
Затем инициализируем нужные ножки на вход/выход и таймер для динамической индикации.

Теперь удаляем ненужные нам обработчики событий EVENT_USB_Device_Connect и EVENT_USB_Device_Disconnect, которые вызываются при присоединении или отсоединении устройства (если не вдаваться в подробности).

В функции EVENT_USB_Device_ConfigurationChanged тоже удаляем всё, что связано со светодиодами (вызов функции LEDs_SetAllLEDs).

В функции EVENT_USB_Device_ControlRequest нужно подправить размеры массивов, которые буду использоваться для хранения входных и выходных репортов:


void EVENT_USB_Device_ControlRequest(void)
{
  switch (USB_ControlRequest.bRequest)
  {
    case HID_REQ_GetReport:
      if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
      {
        uint8_t GenericData[IN_REPORT_SIZE];
        CreateGenericHIDReport(GenericData);

        Endpoint_ClearSETUP();

        /* Write the report data to the control endpoint */
        Endpoint_Write_Control_Stream_LE(&GenericData, sizeof(GenericData));
        Endpoint_ClearOUT();
      }

      break;
    case HID_REQ_SetReport:
      if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
      {
        uint8_t GenericData[OUT_REPORT_SIZE];

        Endpoint_ClearSETUP();

        /* Read the report data from the control endpoint */
        Endpoint_Read_Control_Stream_LE(&GenericData, sizeof(GenericData));
        Endpoint_ClearIN();

        ProcessGenericHIDReport(GenericData);
      }
      break;
  }
}

Эта функция вызывается тогда, когда устройство получает запрос от хоста через нулевую конечную точку. Стандартные запросы обрабатываются библиотекой. Тут мы обрабатываем запросы GET_REPORT и SET_REPORT, которые, как можно догадаться по их названиям, используются для отправки выходных репортов от хоста к устройству и получения входных репортов хостом от устройства. Есть одно но. Через нулевую конечную точку передаются только Feature-репорты и выходные репотры, при отсутствии конечной точки типа Interrupt OUT. У нас же используются Input и Output репорты, которые предаются по соответствующим Interrupt конечным точкам. Эта задача ложится на функцию HID_Task. В ней нужно изменить размеры массивов, так же, как и в предыдущем случае:

void HID_Task(void)
{
  if (USB_DeviceState != DEVICE_STATE_Configured)
    return;
  Endpoint_SelectEndpoint(GENERIC_OUT_EPNUM);
  if (Endpoint_IsOUTReceived())
  {
    if (Endpoint_IsReadWriteAllowed())
    {
      uint8_t GenericData[OUT_REPORT_SIZE];
      Endpoint_Read_Stream_LE(&GenericData, sizeof(GenericData), NULL);
      ProcessGenericHIDReport(GenericData);
    }
    Endpoint_ClearOUT();
  }

  Endpoint_SelectEndpoint(GENERIC_IN_EPNUM);
  if (Endpoint_IsINReady())
  {
    uint8_t GenericData[IN_REPORT_SIZE];
    CreateGenericHIDReport(GenericData);
    Endpoint_Write_Stream_LE(&GenericData, sizeof(GenericData), NULL);
    Endpoint_ClearIN();
  }
}

Теперь остаётся написать функции CreateGenericHIDReport и ProcessGenericHIDReport, которые будут соответственно создавать и обрабатывать репорты.

Начнём с CreateGenericHIDReport. Тут всё просто — нужно прочитать состояние кнопки и записать его в байт, по указателю, переданному в параметре функции:

void CreateGenericHIDReport(uint8_t* DataArray)
{
  if (BUTTON_PIN & (1 << BUTTON_PIN_N)) DataArray[0] = 0;
  else DataArray[0] = 1;
}


Теперь ProcessGenericHIDReport. Тут нам нужно достать 32 байта из массива и записать их в буфер, который потом в прерывании таймера будет отправляться в регистры матрицы.

void ProcessGenericHIDReport(uint8_t* DataArray)
{
  memcpy((void*) buffer, DataArray, BUFFER_SIZE);
}


Пердварительно нужно описать сам буфер

  volatile uint8_t volatile buffer[BUFFER_SIZE];


И собственно прерывание:

ISR(TIMER0_COMPA_vect)
{
  SPI_SendByte(buffer[row*2]);
  SPI_SendByte(buffer[row*2+1]);
  SPI_SendByte(((1 << row) & 0xFF00) >> 8);
  SPI_SendByte((1 << row) & 0xFF);
  MTRX_PORT |= LATCH_PIN;
  MTRX_PORT &= ~(LATCH_PIN);
  row++;
  row &= 15;
}


И последний файл — makefile.
Тут всё просто, изменяем настройки на нужные нам:

  MCU = at90usb162
  BOARD = NONE
  F_CPU = 16000000
  LUFA_PATH = .


Теперь запускаем make (естественно для этого должено быть установлено соответствующее ПО :)) и получаем файл Generic.hex, который можно залить в контроллер любым возможным способом (например с помощью USB-бутлоадера).
Итог:
AVR Memory Usage
— Device: at90usb162

Program: 3406 bytes (20.8% Full)
(.text + .data + .bootloader)

Data: 46 bytes (9.0% Full)
(.data + .bss + .noinit)

С прошивкой всё.

3. Software
Для написания программы для PC, используем небольшую мульти-платформенную библиотечку HIDAPI и для разнообразия… Qt+QtCreator.
Создаём новый проект Qt Widget GUI приложение Qt с базовым классом QMainWindow. Скачиваем последнюю версию HIDAPI и копируем в папку к нашему проекту файл hidapi.h и файл hid.c для нужной ОС. hid.c, само собой, необходимо добавить в проект.
В редакторе форм создадим две кнопки:

Класс главного окна опишем следующим образом:

#include "hidapi.h"

#define SCREEN_SIZE 32
/*Размер выходного HID репорта*/

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow();

private:
  Ui::MainWindow *ui;
  hid_device *handle_device; /*указатель на структуру,
  описывающую HID-устройство в HIDAPI*/
  struct
  {
    uint8_t reportID;
    uint8_t screen[SCREEN_SIZE];
  } report;
   /*Выходной репорт. HIDAPI функции требуют наличия байта
ReportID, даже если устройство его не использует*/

private slots:
  void connectSlot(); /*слот, выполняющий подключение к устройству*/
  void squareEffectSlot(); /*слот, подготавливающий данные для отправки на устройство*/
  void paintSlot(); /*слот, выполняющий обмен данными с устройством*/
};


Текст собственно программы:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
  ui->setupUi(this);
  
  /*соединяем сигналы кнопок со слотами*/
  connect(ui->connect_button, SIGNAL(clicked()), SLOT(connectSlot()));
  connect(ui->square_effect_button, SIGNAL(clicked()), SLOT(squareEffectSlot()));
  /*кнопку, запускаюшую отрисовку делаем неактивной*/
  ui->square_effect_button->setEnabled(false);

  /*обнуляем репорт*/
  memset(&report, 0, sizeof(report));
}

MainWindow::~MainWindow()
{
  delete ui;
}

void MainWindow::connectSlot()
{
  QString product_string("LUFA Generic HID Demo");

  struct hid_device_info *devs, *cur_dev;
  /* запускаем поиск hid-устройств*/
  devs = hid_enumerate(/*VID*/0x03eb, /*PID*/0x204f);
  /*все найдённые устройства помещаются в динамический список*/
  cur_dev = devs;
  while (cur_dev)
  {
    /*пытаемся найти устройство с заданной строкой описания продукта*/
    if (QString::fromWCharArray(cur_dev->product_string) == product_string)
      break;
    cur_dev = cur_dev->next;
  }
  /*если нашли нужное устройство, и оно успешно открылось...*/
  if (cur_dev && (handle_device = hid_open_path(cur_dev->path)))
  {
    /*то активируем кнопку*/
    ui->square_effect_button->setEnabled(true);
    ui->connect_button->setEnabled(false);
  }
  else
    QMessageBox::critical(this, tr("Ошибка"), tr("Невозможно подключиться к устройству"));

  /*удаляем список*/
  hid_free_enumeration(devs);
}

/*слот, вызываемый при нажатии кнопки "Square effect"*/
void MainWindow::squareEffectSlot()
{
  static QTimer* timer = NULL;
  if (timer == NULL)
  {
    timer = new QTimer;
    connect(timer, SIGNAL(timeout()), SLOT(paintSlot()));
    timer->start(20);
  }
  else
  {
    timer->stop();
    disconnect(timer);
    delete timer;
    timer = NULL;
  }
}

/*Слот, вызываемый по таймеру, рисует узоры :)*/
void MainWindow::paintSlot()
{
  static uint8_t x, y, min, max = 15, state;
  if (state < 4)
  {
    /*устанавливаем нужный бит в репорте*/
    report.screen[(y & 15) * 2 + (x & 15) / 8] |= (1 << (7 - (x & 7)));
  }
  else
  {
    /*сбрасываем нужный бит в репорте*/
    report.screen[(y & 15) * 2 + (x & 15) / 8] &= ~(1 << (7 - (x & 7)));
  }
  /*AGGRRRRRHH))*/
  /*тут что-то совсем неинтересное)*/
  switch (state & 3)
  {
    case 0:
      x++;
      if (x==max) state++;
    break;
    case 1:
      y++;
      if (y==max) state++;
    break;
    case 2:
      x--;
      if (x==min)
      {
        state++;
        min++;
      }
    break;
    case 3:
      y--;
      if (y==min)
      {
        max--;
        state &= ~3;
      }
      if (x==7 && y==7)
      {
        min = 0;
        max = 15;
        x = 0;
        y = 0;
        if (state < 4) state = 4;
        else state = 0;
      }
    break;
  }

  /*Вот самое интересное - отправка и приём репортов. Всё проще некуда :)*/
  uint8_t but = 0;
  if (hid_write(handle_device, (const unsigned char*)&report, sizeof(report))==-1 ||
      hid_read(handle_device, &but, 1)==-1)
  {
    QMessageBox::critical(this, tr("Ошибка"), tr("Ошибка при отправке или приёме данных"));
    qApp->quit();
  }
  if (but) QMessageBox::information(this, tr("Информация"), tr("Была нажата кнопка"));
}


Собственно, из всего этого полотна интерес представляют четыре функции: hid_enumerate, hid_open_path, hid_write и hid_read.
Функция

  hid_device_info* hid_enumerate(unsigned short vendor_id, unsigned short product_id);

возвращает указатель на первое звено списка структур, описывающих все найденные hid-устрйоства с заданными vid/pid. В звеньях списка нас интересуют только поля product_string и path. По product_string осуществляется поиск, а path используется в функции

  hid_device* hid_open_path(const char *path);

которая используется для того, чтобы «открыть» устройство по нужному пути. К слову сказать, на Windows этот путь выглядит примерно так
\\?\hid#vid_03eb&pid_204f#6&1f2bfd23&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
Также, HIDAPI имеет функцию

  hid_device* hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number);

для «открытия» устройства. Можно было воспользоваться ею, но тогда следует иметь в виду, что в сисьтеме должно быть всего одно устройство с vid/pid и серийный номером (если он используется), передаваемыми в параметрах.

И напоследок функции

int hid_write(hid_device *device, const unsigned char *data, size_t length);
int hid_read(hid_device *device, unsigned char *data, size_t length);

о назначении которых несложно догадаться — запись выходного репорта в HID-устройство и чтение входного репорта. И тут есть один странный нюанс. Если HID-устройство не использует ReportID, то есть не имеет несколько входных или выходных репортов, то в функцию write всё равно нужно отправлять репорт, в котором первым байтом является ReportID и он должен быть равен нулю. При чтении входного репорта функцией hid_read никаких дополнительных байтов не появляется.

Теперь осталось добавить в .pro файл строчку

  LIBS += -lsetupapi

для сборки на Windows.
Собираем проект, запускаем…


Мда, телефон сегодня не в настроении.)

4. Ссылки.
Исходники из статьи
LUFA
LUFA online docs
LUFA online docs по-русски (старая версия)
HIDAPI
Qt
  • +3
  • 05 февраля 2012, 21:53
  • QBasic
  • 1
Файлы в топике: Sources.zip

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

RSS свернуть / развернуть
Унес в AVR
0
а с feature-репортами не пробовали работать?
0
Пробовал, принцип тот же, только все репорты пойдут через нулевую конечную точку.
0
Понять бы как в STM32 репорты через нулевой ендпоинт принимать, естесственно использую стек от ST.
0
А где этот стек можно взять? Сходу что-то не нашлось.
0
Вот библиотека и пользовательское руководство:
UM0424 User manual STM32 USB-FS-Device development kit
STM32_USB-FS-Device_Lib_V3.3.0
0
Так, поглядел. Какое-то оно всё… хм, ладно.
Если переделывался пример из папки Project\Custom_HID\, то, по всей видимости, надо допиливать функцию CustomHID_Data_Setup и добавлять туда реакцию на запрос SET_REPORT, если я правильно понял логику работы библиотеки.
0
Добавил туда обработку на HID запросы. Но что-либо считывать или записать через нулевой ендпоинт не получается пока что.
0
Огромное спасибо за статью, как раз то что нужно! На какой частоте у вас работает at90usb162?
0
На 16 МГц.
0
А эта частота где то в библиотеке LUFA задается? Я вот видел еще одну похожую библиотеку http://www.gaw.ru/html.cgi/txt/app/micros/usb/avr_series2.htm Вы с такой не работали? Посоветуйте какую лучше использовать?
0
Она же в makefile задаётся:
F_CPU = 16000000


О, Atmel'овкий USB-стек. LUFA однозначно лучше, тут написано почему.
От себя добавлю: библиотеку от Atmel мне даже запустить не удалось.)) Кроме того, где-то там в коде я видел ошибки и банальные опечатки. Да и сам код написан несколько небрежно.
0
Огромное спасибо!
0
Мда. Как я вижу автор не рискнул создать свой проект с LUFA а переделал стандартный.
Как подключить LUFA к своему проекту а не делать очередную переделку?
0
  • avatar
  • a9d
  • 11 февраля 2012, 02:28
Можно было и свой создать, но тогда написалось бы абсолютно то же самое, что есть в стандартном примере. Ну и статья собственно не о одной LUFA, т.к. возможности библиотеки достаточно широкие — там наверное нехилый цикл статей вышел бы, если подробно её описывать.

Как подключить к своему проекту — так просто не объяснить, опять же проще посмотреть в примере (кстати это большой плюс библиотеке — много подробных примеров). Если в двух словах — надо подключить заголовочник USB.h, создать свои дескрипторы устройства, конфигураций, интерфейсов, конечных точек и т.д. (что требуется по спецификации и конкретной задаче), написать обработчик запросов этих дескрипторов ( CALLBACK_USB_GetDescriptor ), и либо воспользоваться встроенной в библиотеку функцией обработки специфических запросов (для нужного класса USB-устройств), либо написать свою. Ну и плюс собственно «полезная нагрузка».
0
Ах, да, ещё конечно же дефайны настроек либы, и, само собой, компиляция исходников библиотеки + сборка всего этого вместе со своим проектом.
0
Лучше бы ты сразу расписал эту процедуру. А то я два дня протрахал с подключением LUFA к scmRTPOS. И таки скрестил их.
Эта процедура нигде не расписана, пришлось все самому делать.
0
Ура)) Все выходные пытался настроить HID используя ClassDriver. Ну ни в какую, чтение работает но запись не пашет. Переделал все под LowLevel и все заработало с первой попытки!
0
  • avatar
  • a9d
  • 14 февраля 2012, 02:57
А это вообще нормально, что постоянно вызывается CreateGenericHIDReport из HID_task ??
0
  • avatar
  • a9d
  • 26 февраля 2012, 19:41
Все, нашел ответ Дина на эту ситуацию. Это нормально. Если постоянная отправка не требуется, то нужно самому в коде предусмотреть условие для отправки.
0
Можно по подробнее про условие для отправки? Если используется interrapt in endpoint, значит ли это, что устройство каждый раз при опросе хостом конечной точки interrapt in на наличие данных, отправляет input report на хост? С Наступающим всех!
0
возник ряд вопросов у меня тот же самый контроллер. Вопрос 1 обязательно ли нужен бутлоадер чтобы это все работало? Вопрос 2 Program: 3406 bytes (20.8% Full)
(.text + .data + .bootloader) У меня один бут лоадер больше весит а получающийся хекс фпйл и того более в разы! с чем это может быть связано? Вопрос 3 HWBE pin как то влияет на работу всего этого дела? (Мне для начало хотелось бы реализовать просто прошивку чтобы девайс определялся как HId устройства при подключении к пк, но после компиляции и прошивки при подключении к пк устройство не определяется не пойму с чем это связано) Заранее большое спасибо
0
1) Бут не обязателен. Это протсто бутлоадер.
2) Скорей всего компилятор выбросил код который ненужен. Логику программы нужно смотреть.
3) Пин бутлоадера влияет только на бутлоаадер. В даташите описано его поведение.
0
Логики никакой программа которая отправляет 8 байт и принимает 8 байт и все собственно! Все сделано как в исходниках и как описано в статье. У меня есть предположение что я не правильно использую авргцц я не работал никогда с ним
0
У меня один бут лоадер больше весит а получающийся хекс фпйл и того более в разы!
У тебя бутлоадер уже зашит или включен в эту прошивку? Скорее всего первое, в этом случае бут не считается (поскольку в самой прошивке его нет, а что там у тебя в МК компилер и знать не знает).
Хекс мало того, что ASCII HEX (уже +100% к размеру), так еще и с дополнительной инфой. Реально двоичных данных там раза в три меньше, чем .hex весит.
0
нет бут лоадер не куда не зашит и не включен просто отдельный файл скачанный с интернета не суть дело вобщем то! Суть дела в том, что вроде все по примеру а после прошивки девайс не определяет при подключении к пк, вот в чем собственно загвоздка и в чем дело никак разобраться не могу пока что.
0
нет бут лоадер не куда не зашит и не включен просто отдельный файл скачанный с интернета
Один хрен, тащемта — в прошивку он в любом случае не включается.
Суть дела в том, что вроде все по примеру а после прошивки девайс не определяет при подключении к пк
Вариантов много. Скорее всего ты где-то ошибся. «Не определяется» — это «устройство не опознано» или винда вообще не видит что к ней что-то подключили?
0
вообще не видит устройства
0
Неплохо бы было проект показать, чтоб не гадать. С «железной» частью всё в порядке? Другие примеры работают?
0
С железкой все нормально. Исходники выложу в скором времени.
0
У меня этот пример запустился, устройство определяется. Перекомпилил только под 16 МГц.
0
у меня кварц 8 МГц, а схему вашего девайса можно поглядеть?
0
Схема вот как тут, только кварц на 16 МГц.
0
А линковали проект вы в чем?
0
Всё из WinAVR, по мэйкфалу из примера.
0
Совсем тогда странно я тоже вин авром пользовался при линковке, разница только в кварце в 8 мегагерц и не работает хм чудеса
0
Так в чем может быть причина тогда не пойму, Девайс сто процентов рабочий (там кучу всего реализована уарт эспиай и много чего еще и все отлично работает)
0
МП работает от 5 вольт, но с I/O 3 Вольта, это не может влиять на то что девайс не определяется?
0
Не подскажите как интегрировать проект LUFA в AVR studio????
0
В какой AVR Studio?
0
И какой проект.)
0
Avr Studio 5 скажем вот этот проект Там я нашел можно указать makefile по которому собирать проект но он не собирается а сыпит ошибки
0
Дело в том, что этот makefile использует некоторые утилитки, которых нет в avr-toolchain. Поэтому надо раздобыть WinAVR или MSYS и в настройках студии (Tools->Options->AVR Toolchain->Configure Make Utility) указать путь до bin-папки (WinAVR\utils\bin или MSYS\bin) и перезапустить студию.
0
а зачем нужен вообще этот makefile? Там всего лишь прописаны пути к LUFA.
0
Хорошая статья, было бы отлично Вы написали такую же статью, только для библиотеки v-usb. Думаю многим бы она была полезной.
0
  • avatar
  • SpulN
  • 26 декабря 2012, 10:01
Так есть же очень хорошая статья.
0
Спасибо, статью я конечно видел. Мне интересно именно чтобы был один input report и один output и софт на hid api.
0
Так софт на комп вполне подходит и отсюда.)
Репорты соорудить — тоже не проблема, всё остальное в той статье есть.)
0
Описал свою проблему на форуме Your text to link...
0
Можно ли просить автора помочь с подобным проектом в личке? Затык… софт на дельфях с TJvHidDevice
0
Сейчас разбираюсь с передачей данных через USB, использую библиотеку Люфа. Устройство определяется. Осталось понять как принимать и передавать пакеты. Вот тут я завис. QBasic, вы пробовали работать с библиотекой AtUSBHID? Можно ли на ней реализовать передачу, не используя Feature репорт?
0
С библиотекой AtUSBHID не работал. Просмотрел доки — по всей видимости, надо воспользоваться функциями writeData и readData.
0
Автор, большое спасибо за статью! Подскажите пожалуйста, на каком пине у контроллера у Вас кнопка? Где в коде программы микроконтроллера это прописано?
0
Порт D, пин 7 Почти в самом начале:
#define BUTTON_PORT PORTD
#define BUTTON_DDR DDRD
#define BUTTON_PIN PIND
#define BUTTON_PIN_N PD7
0
И еще маленький вопрос. Для чего нужен report.screen в void MainWindow::paintSlot()? Куда и как он передает значения?
0
Это массив с полезными данными, который отправляется в устройство с помощью функции hid_write. В void MainWindow::paintSlot() он нужен для того, чтобы его заполнить какими-то данными для последующей отправки.
0
Пытаюсь разбираться в Вашем примере, хочу его немного расширить. К выводам PB4 и PB5 подсоединил еще по кнопке. И возникает вопрос: как передавать через DataArray[0] различные значения, например при нажатии на кнопки PB4 и PB5? Какой длины для этого потребуются репорты? Спасибо!
0
Репорт можно не менять, там целый байт пересылается. Для других кнопок можно использовать оставшиеся 7 бит от этого байта.
0
Вот такой код написал в QT:
void MainWindow::paintSlot()
{
uint8_t but=0;
hid_write(handle_device, (const unsigned char*)&report, sizeof(report));
hid_read(handle_device, &but, 1);
if (but==0) ui->lineEdit->setText(«nichego»);
if (but==1) ui->lineEdit->setText(«1»);
if (but==2) ui->lineEdit->setText(«2»);
if (but==3) ui->lineEdit->setText(«3»);
}
Вот такой в программе контроллера:
void CreateGenericHIDReport(uint8_t* DataArray)
{
if (PINB & (1 << PB4))
{
DataArray[0]=1;
}
if (PINB & (1 << PB5))
{
DataArray[0]=2;
}
if (PIND & (1 << PD7))
{
DataArray[0]=3;
}
else
{
DataArray[0]=0;
}

Где может быть ошибка? На PB4 и PB5 реакции нет, на PD7 выдает but==0, ui->lineEdit->setText(«nichego»);
Вероятно, я где-то жестко туплю, но все же…
0
Чтобы код не превращался в нечитаемую кашу, следует использовать тег <code>.

По поводу кода — все правильно, последнее условия запишет в DataArray[0] значение 0 или 3, затерев любое значение, выставленное предыдущими проверками. Если ты хотел сделать сорт оф switch, то надо было использовать конструкцию else if (ЕМНИП, elseif в C нет):
if(cond1) {action1}
else if(cond2) {action2}
else if(cond3) {action3}
else {defaction};

Но вообще-то, разумней передавать все три кнопки одновременно. Первую — в нулевом бите, вторую — в первом бите, третью, соответственно, во втором.

Алсо, по видимому кнопки у тебя включены по стандартной схеме — придавливают к земле. Если так, то условия у тебя неправильные.
0
Спасибо большое за ответ!
Говорил же, туплю, так и есть) Ошибку понял.
По поводу передачи всех 3 кнопок в одном байте, выглядит примерно так?

DataArray[0]=1;
DataArray[1]=1;
DataArray[2]=1;
...

А как тогда их отлавливать в программе в QT?
Не посоветуете какую-нибудь универсальную программку типа Terminal, но для USB HID устройств?
0
В твоем варианте три байта. Словосочетание «битовые операции» о чем-нибудь говорит? Если нет — самое время подучить матчасть.
0

DataArray[0]|=(1<<0);
DataArray[0]|=(1<<1);
DataArray[0]|=(1<<2);
...

Это они?)))
Не пинайте сильно, программирование и электроника не основная моя работа, руки доходят не очень часто, все забывается без практики.
0
Да. Только не забудь обнулить переменную прежде чем ставить в ней биты or'ом. И else if, да и вообще else тебе теперь будет не нужен в данном куске кода.
Алсо не забудь изменить условия, при нажатой кнопке в проверяемом бите должен быть ноль.
0
P.S. Под каждым комментарием есть кнопка для ответа конкретно на него. Используй ее.
0
Пытаюсь отправить байт данные о нажатой кнопке, получается ерунда. Подскажите пожалуйста, что я не так делаю?
Передача байта от контроллера:

void CreateGenericHIDReport(uint8_t* DataArray)
{
	if (PINB & (1 << PB4))

        {
            DataArray[0]=1;
        }

    else

       {
            if (PINB & (1 << PB5))

                    {
                        DataArray[0]=2;
                    }

            else

                {
                   if (PIND & (1 << PD7))

                   {
                       DataArray[0]=3;
                   }

                    else
                    {
                        DataArray[0]=0;
                    }
                }

        }

Прием байта от контроллера:

hid_read(handle_device, &but, 1);

   switch(but){

                      case 0:
                              {
                                ui->lineEdit->setText("0");
                                break;
                              }

                      case 1:
                              {
                                ui->lineEdit->setText("1");
                                break;
                              }

                      case 2:
                              {
                                ui->lineEdit->setText("2");
                                break;
                              }

                      case 3:
                              {
                                ui->lineEdit->setText("3");
                                break;
                              }

                      default:
                              {
                                ui->lineEdit->setText("something");
                                break;
                              }

  }
0
Все, начиная с проверки на нажатие кнопки.
void CreateGenericHIDReport(uint8_t* DataArray)
{
        DataArray[0] = 0;
        if (!(PINB & (1 << PB4)))
        {
                DataArray[0] |= 0x01;
        };
        if (!(PINB & (1 << PB5)))
        {
                DataArray[0] |= 0x02;
        };
        if (!(PIND & (1 << PD7)))
        {
                DataArray[0] |= 0x04;
        };
}

hid_read(handle_device, &but, 1);
if (but & 0x01)
{
    //first button
};
if (but & 0x02)
{
    //second button
};
if (but & 0x04)
{
    //third button
};
+1
Большое спасибо! Все получилось!
0
Реализовал измерительный прибор с передачей данных на ПК по USB на библиотеке Lufa. Устройство было успешно распознано. ПО хоста писалось на HID API за основу был взят этот проект на QT. Ошибка о не правильной передаче данных не возникает. Подскажите, как вывести сам байт данных с помощью функции hid_read?
0
Вот так выглядит данные передаваемые хосту.
void CreateGenericHIDReport(uint8_t* DataArray)
{
	  DataArray[0] = result;
}

Подскажите где они записаны в функции hid_read_timeout? Заранее спасибо
0
hid_read(handle_device, &but, 1)==-1 вот тут описан прием одного байта, а как сделать прием нескольких байт в QTCreator? Через массив байт?
0
Теперь запускаем make
А вот с этого момента поподробней пожалуйста. Как мы его запускаем? Где он может быть? Это работа в командной строке? Что прописываем? Для людей не осведомленных — темный лес.
0
Make. Да, это командная строка.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.