В данной статье речь пойдет о таком довольно популярном интерфейсе как USB, точнее как использовать этот интерфейс USB на микроконтроллерах семейства AVR. В интернете на данную тему много информации но большинство из нее представлено отдельными фрагментами и не дает полную картину о то как использовать этот интерфейс на микроконтроллерах AVR.
Такие интерфейсы как COM, MIDI, LPT широко использовались в свое время, но сейчас они морально устарели и все реже и реже встречаются в современных компьютерах в основном в компьютерах для промышленного и узкоспециализированного оборудования. Для связи собранного устройства с компьютером можно использовать всевозможные переходники конвертеры и эмуляторы, но как показывает практика они порождают множество проблем.
Для того чтобы использовать подружить USB и AVR можно пойти 3-мя путями:
Использовать аппаратную поддрежку USB интерфейса, к примеру микроконтроллера AT90USB*. Для того чтобы его использовать нужно написать для него особую прошивку. И если вашу собраное устройство не стандартного класса USB то придется для компьютера писать драйвер, который объяснит ПК с каким устройством имеем дело.
Использовать конверторы USB в какой нибудь другой интерфейст (к примеру USB-UART на микросхеме FTDI FT232RL). В качествое других интерфейсов может выступать RS232, I2C или другие. При таком подходе нам не нужно замарачиваться и знать как работает интерфейс USB, также нужда в драйвере для компьютера тоже отпадает, так как драйвера для таких конверторов уже написаны производителем.
Можно сделать все хитрее взять микроконтроллер AVR и написать для него программу которая будет эмулировать работу USB. При таком подходе возникает трудность в реализации большой скорости передачи данных. Интерфейс USB работает на большой скорости которая может быть: LowSpeed - 1,5 Mbit/s, FullSpeed - 12 Mbit/s, HighSpeed - 480 Mbit/s, а у интерфейса USB 3.0 скорость может быть еще выше. Поэтому на микроконтроллерах AVR можно сделать устройство только со скоростью LowSpeed. Для большинства самодельных устройств такой скорости вполне достаточно.
Мы будем использовать способо программной эмуляции. В настоящее время уже есть три готовых проекта под микроконтроллеры AVR для эмуляции интерфейса USB.
Igor Cesko первый кто сделал такой проект, он был написан на языке assembler.
После появился проект V-USB который написан на языке C с использованием ассемблерного кода Используя V-USB был сделан проект ИК приемника для компьютера.
USBtiny в свою очередь является произволным от ранней версии проекта V-USB. По возможности он уступает V-USB но зато гараздо проще теоретически и относительно легок в освоении.
Мы будем использовать проект V-USB
Он имеет следующие основные преимущества:
В своих USB экспериментах, прошивку для микроконтроллера писал на языке C в среде AVR-Studio 4 + WinAVR, программа для ПК была разработана при помощи среды Borland C++ Builder как самый простенький и хустрый вариант. Поэтому все следующие примеры будут такими же. От правильного выбора инструментов зависит количество головной боли на последующих шагах. По поводу того что лучше C или Assembler можно говорить много. Как по мне то нужно владеть всеми инструментами, каким-то в большей степени каким-то в меньшей и использовать тот или другой в зависимости от конкретной поставленной задачи.
Считаю важным сказать пару слов про идентификаторы VID и PID которые представляют 16-битные числа. Эти числа использует операционная система и определяет какой подгрузить драйвер. Стоимость Vendor-ID на usb.org составляет 2000$. По поводу легальности использования VID/PID можно прочитать на станчике эмбеддера BSVi. V-SUB предоставлят свободную пару VID/PID. У вас наверное возникнет вопрос "Как подключить сразу несколько устройст с однинаковыми значениями VID/PID". Подключить несколько устройств с одниковами значениями вы можете, ничего страшного в этом нет так как каждое устройство USB имеет также свой VENDOR_NAME и DEVICE_NAME идентификаторы.
Давайте попробуем собрать свое первое USB HID устройство на микроконтроллере Atmega8 и научим его общаться с компьютером при помощи интерфейса USB. Может возникнуть вопрос "почему именно HID устройство". Ответ самый простой- потому что мы не хотим замарачиваться,выносить себе мозг и писать всякие там драйвера под Windows. А когда мы подключаетм HID устройство то операционная система сама выбирает и включает необходимый драйвер. Наша программа под компьютер не будет нуждаться в установки и настройки чего либо она сразу будет использовать готовый алгоритм для работы с HID устройством.
Для работы нам понадобятся следующие программы:
Микроконтроллер ATmega8 подключается по классической 5-ти вольтовой схеме питания. Другие схемы можно посмотреть в архиве V-USB в каталоге cirtuits. В интерфейсе USB на линиях D+ и D- уровень сигнала составляет 3,3 В, а вся наша схема работает от напряжения в 5 В. Поэтому нужно согласовать уровни USB с нашей схемой, для этого устанавливаются стабилитроны D1 и D2, которые снижают сигнал с микроконтроллера на гасящих резисторах R3 и R4 до требуемого уровня в 3,3 В необходимого стандарту. Для определения версии протокола используется делитель напряжения на резисторах R1 и R2 который создает на линии D- уровень в 3,4 В в режиме холостого хода. Если использовать другой номинал резистора 1,5 кОм вместо указанного R1 2,2 кОм, или если не установить резистор R2 вовсе то вместо 3,4 В получится 3,7 В что приведет к открыванию стабилитрона D1 который уменьшит его до напряжения открывания 3,4 В - 3,5 В. В итоге у нас получится перекос по току на холостом ходу (стабилитрон D2 получится не нагружен совсем, а по D1 уже будет течь ток) и это будет отражаться на длинных проводах USB. Значения сопротивлений R3 и R4 опреляются по току, в данной USB AVR схеме они равны значению в 68 Ом.
Ассесмблерная часть V-USB написана с поддержкой следующего ряда частот: 12 МГц, 12,8 МГц,15 МГц,16 МГц,16.5 МГц, 18 МГц, 20 МГц. Другие частоты не поддерживаются. Данная частота вручную нигде не прописывается, она определяется в конфигурации проект AVR Studio. Proget>Configuration Options>General(вкладка)> Frequency (поле). Частота указывается в Герцах, если не правильно выставить частоту то при компиляции всплывет куча предупреждений и программа так и не будет скомпелированна. В студии сществует переменная F_CPU которую использует компилятор и она доступна всему проекту, V-USB также использует его. Для правильной работы кварца необходимо правильно выставить FUSE биты микроконтроллера.
12 Мгц - частоиспользуемая частота для V-USB, она является минимальной частотой при которой возможна эмуляция всех необходимых таймингов спецификации USB;
15 МГц - близка к 12 МГц, местами выставлены NOP-ы. Использование такой частоты делает код несколько меньше, это связано с тем что большая частота позволяет чаше использовать циклы;
16 МГц - эта частота была добавлена для пользователей Arduino и других плат которые имеют кварцевый резонатор на 16 МГц. Ассемблерная реализация данной частоты была написна с некоторыми тонкостями которые применяют циклы замедления. Это происходит потому что 16 МГц не возможно разделить нацело на USB low speed bit clock;
12.8 МГц и 16,5 МГц - данные частоты предназначены для тактирования от внутреннего RC генератора, точность составляет 1%.
18 МГц - такая частота наиболее близка к стандартам USB. Использование такой частоты позволяет производить проверку входящих пакетов CRC прямо на лету. Если пакеты имеют не верную контрольную сумму то они отклоняются.ТАкже существует опция проверки данных на целостность на уровне программы.
20 МГц - для тех кто любит большие скоростя. Так как 20 МГц не делится нацело на USB speed bit clock 1,5 Мгц. То применяются циклы замедления как и при частоте 16 МГц.
Создаем проект AVR GCC, назовем его например Hid_example_firmware. И начнем писать нашу прошивку.
Далее нам необходимо скопировать все файлы с ранее скаченного архиваvusb.tar.gz в каталог проекта. Далее добавляем в проект следующие файлы:
Для того чтобы добавить файла нужно выбрать в контекстном меню "Add existing Source File(s)"
Далее нам необходимо корректно настроить файл usbconfig.h и также его поместить в папку с проектом. Inline комментарии usbconfig.h была переведена на русский язык. Код приведен ниже.
Теперь правильно настроим usbconfig.h и также поместим в каталог с проектом (ссылка рабочего проекта-примера в конце статьи). Inline документацию usbconfig.h я перевел на русский (использовал перевод от microsin.ru + дополнял сам). Там очень много опций, в статье опишу самые необходимые для быстрого старта (подробности смотрим внутри файла).
#define USB_CFG_IOPORTNAME D /* Указан порт, к которому подключена шина USB. Если Вы сконфигурируете "B", * будут использоваться регистры PORTB, PINB и DDRB. */ #define USB_CFG_DMINUS_BIT 4 /* Это номер бита в USB_CFG_IOPORT, куда подключен сигнал D-, может * использоваться любой бит в порте. */ #define USB_CFG_DPLUS_BIT 2 /* Это номер бита в USB_CFG_IOPORTNAME, куда подключен сигнал D+, может * использоваться любой бит в порте. Пожалуйста, примите во внимание, что D+ * должен быть также подсоединен к ножке прерывания INT0! [Вы можете также * использовать другие прерывания, см. секцию "Optional MCU Description" далее, * или Вы можете подсоединить D- к прерыванию, как это необходимо если Вы * используете опцию USB_COUNT_SOF. Если Вы используете D- для прерывания, * оно будет срабатывать также от маркеров Start-Of-Frame каждую * милисекунду.] */ #define USB_CFG_VENDOR_NAME 'w', 'e', '.', 'e', 'a', 's', 'y', 'e', 'l', 'e', 'c', 't', 'r', 'o', 'n', 'i', 'c', 's', '.', 'r', 'u' #define USB_CFG_VENDOR_NAME_LEN 21 /* Здесь указывают имя вендора (vendor name), возвращаемое устройством. * Имя должно предоставляться как список символов в одиночных * кавычках, а USB_CFG_VENDOR_NAME_LEN задает их количество. Символы * интерпретируются как Unicode (UTF-16). Если Вам не нужно имя вендора, * закомментируйте этот макрос. ВСЕГДА указывайте имя вендора, содержащее Ваше * доменное имя Internet, если Вы используете свободно распространяемую пару * obdev VID/PID. За деталями обращайтесь к файлу USB-IDs-for-free.txt. */ #define USB_CFG_DEVICE_NAME 'H', 'i', 'd', ' ', 'e', 'x', 'a', 'm', 'p', 'l', 'e' #define USB_CFG_DEVICE_NAME_LEN 11 /* Здесь указывается имя устройства (device name) таким же способом, как и в * предыдущем параметре указывается имя вендора. Если Вам не нужно имя * устройства, закомментируйте этот макрос. См. файл USB-IDs-for-free.txt * перед назначением имени, если Вы используете свободно распространяемую * пару obdev VID/PID. */ #define USB_CFG_INTERFACE_CLASS 3 /* Установленное значение 3 означает что наше устройство будет * принадлежать класу HID. */
Перед тем как начать писать программу для МК, необходимо разобраться с базовыми принципами работы. HID устройство обменивается с хотом данными, данные поступают блоками фиксированного размера - или репортами. Их структура описана в дискрипторе HID, это дискриптор предоставляет хосту при подключении. Инизиализацию приема и передачу данных осуществляет хост прогамма на компьютере. В случае если хост хочет послать данные устройству то он сначала посылает команду HID_SET_REPORT, при этом V-USB вызывает функцию usbFunctionRead().
Линия данных D+ подключается к прерыванию INT0 так как это прерывание с наивысшим приоритетом. В процессе обмена данных по USB, микроконтроллер постоянно уходит на обработку INT0, на котором как раз и подключен V-USB. И только после этого управление будет передано основной программе. Если вам в вашем устройстве нужно еще и обрабатывать свои прерывания, то нужно выставить глобальный флаг прерывания используя команду sei(), для того чтобы смогло сработать INT0 с целью правильной работы V-USB.
Такая функция как usbPoll() сообщает хосту что подключенное устройство еще находится в живом состоянии и готово к работе, эту функцию нужно вызвать не реже чем 50 ms. Если не выполнять это условие то операционная система Windows напишет следующее " Подключено неизвестное устройство"
Интервал в 50ms - это USB timeout for accepting a Setup message- представляет собой специальную команду хоста в нашем примере HID_SET_REPORT и HID_GET_REPORT.
Функция usbFunctionSetup() занимается setup сообщениями, здесь происходит обработка управляющих команд USB, далее происходит запуск функции usbFunctionRead() или функции usbFunctionWrite().
Объясняя более понятными словами то это константы собранные в массив и зашитые в Flash память устройства, которые необходимы и описывают структуру пакетов данных (HID репортов). В нем содержится инфорамация о количестве пакетов которое поддерживает устройство. Каждый бит и каждый байт в пакете имеет свое назначение. После того как устройство подключается к компьютеру, дескриптор сообщает все свои параметры, в свое очередь операционная система компьютера поймет как следует общаться с таким устройством, к примеру будет знать какие биты ответсвенны за нажатие тех или иных кнопок джостика.
У нас будет "HID совместимое устройство" которое будет передавать разные данные определенного размера (это не мышь и не дждостик). В нашем примере дескриптор предаставляет собой некоторого рода набор произвольных чисел с комментариями. Полезная информация про дескрипторуUSnooBie's USB HID Report Descriptor TutorialиHID Related Specifications.
Меняя дескриптор можно представиться конкретным HID устройством, к примеру клавиатурой или чем либо еще. Интересные примеры на эту тему:Маленькая USB пакостьиThe Haunted USB Cable!. Много интресных примеров находится на сайте V-USB.
С целью передачи данных сделаем структуру, ее использование сделает код удобнее и гибче к переделкам чем простое использование массива. Если нужен массив то в его можно добавить и использовать внутри структуры. В нашем дескрипторе есть один вид репорта который имеет размер 8 бит - REPORT_SIZE, его количество равно размеру передаваемой структуры - REPORT_COUNT. Получается что данные передаются порциями по 8 бит. По умолчанию V-USB поддерживает передачу и прием размером по 254 байта. Если нам нужен размер по больше тогла нам нужено в файле usbconfig.h выставитьUSB_CFG_LONG_TRANSFERS в значение 1, только в этом случае увеличится размер самого драйвера.
Обработку входящих и передаваемых данных внутри микроконтроллера берут на себя две функции:
Для простоты структура данных заполняется внтури этих функций, но так делать не обязательно, заполнить их можно в других участках кода, в этом случае их необходимо объявить как volatile.
Такой парамет как *data представляет собой указатель на буфер V-USB где происхоид чтение и запись данных которые имеют размер len. Такой буффер данных имеет максимальный размер в 1 байт типа uchar и данное значение равняется размеру нашего репорта. А размер нашей структуры имее рамер больше чем 1 байт, именно по этой причине процесс передачи данных происходит по частям. Для этого существуют переменные currentAddress и bytesRemaining в которых хранится информация про текущую передачу.
Запись вида uchar *buffer = (uchar*)&pdata означает:
Получается что мы объявили указатель с именем uchar *buffer на определенное место в памяти где находится(хранится) наша структура. Передача осуществляется кусками uchar.
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include <avr/pgmspace.h> /* нужно для usbdrv.h */ #include "usbdrv.h" struct dataexchange_t // Описание структуры для передачи данных { uchar b1; // Я решил для примера написать структуру на 3 байта. uchar b2; // На каждый байт подцепим ногу из PORTB. Конечно это uchar b3; // не рационально (всего то 3 бита нужно). }; // Но в целях демонстрации в самый раз. // Для наглядности прикрутить по светодиоду и созерцать :) struct dataexchange_t pdata ={0,0,0}; PROGMEM char usbHidReportDescriptor[22]={// USB report descriptor // Дескриптор описывает структуру пакета данных для обмена 0x06,0x00,0xff, // USAGE_PAGE (Generic Desktop) 0x09,0x01, // USAGE (Vendor Usage 1) 0xa1,0x01, // COLLECTION (Application) 0x15,0x00, // LOGICAL_MINIMUM (0) // min. значение для данных 0x26,0xff,0x00, // LOGICAL_MAXIMUM (255) // max. значение для данных, 255 тут не случайно, а чтобы уложиться в 1 байт 0x75,0x08, // REPORT_SIZE (8) // информация передается порциями, это размер одного "репорта" 8 бит 0x95,sizeof(struct dataexchange_t), // REPORT_COUNT // количество порций (у нашем примере = 3, описанная выше структура передастся за три репорта) 0x09,0x00, // USAGE (Undefined) 0xb2,0x02,0x01, // FEATURE (Data,Var,Abs,Buf) 0xc0 // END_COLLECTION }; /* Здесь мы описали только один report, из-за чего не нужно использовать report-ID (он должен быть первым байтом). * С его помощью передадим 3 байта данных (размер одного REPORT_SIZE = 8 бит = 1 байт, их количество REPORT_COUNT = 3). */ /* Эти переменные хранят статус текущей передачи */ static uchar currentAddress; static uchar bytesRemaining; /* usbFunctionRead() вызывается когда хост запрашивает порцию данных от устройства * Для дополнительной информации см. документацию в usbdrv.h */ uchar usbFunctionRead(uchar *data, uchar len) { if(len > bytesRemaining) len = bytesRemaining; uchar *buffer =(uchar*)&pdata; if(!currentAddress) // Ни один кусок данных еще не прочитан. { // Заполним структуру для передачи if( PINB & _BV(1)) pdata.b1 =1; else pdata.b1 =0; if( PINB & _BV(2)) pdata.b2 =1; else pdata.b2 =0; if( PINB & _BV(3)) pdata.b3 =1; else pdata.b3 =0; } uchar j; for(j=0; j<len; j++) data[j]= buffer[j+currentAddress]; currentAddress += len; bytesRemaining -= len; return len; } /* usbFunctionWrite() вызывается когда хост отправляет порцию данных к устройству * Для дополнительной информации см. документацию в usbdrv.h */ uchar usbFunctionWrite(uchar *data, uchar len) { if(bytesRemaining ==0) return1; /* конец передачи */ if(len > bytesRemaining) len = bytesRemaining; uchar *buffer =(uchar*)&pdata; uchar j; for(j=0; j<len; j++) buffer[j+currentAddress]= data[j]; currentAddress += len; bytesRemaining -= len; if(bytesRemaining ==0) // Все данные получены { // Выставим значения на PORTB if( pdata.b1 ) PORTB |= _BV(1); else PORTB &=~_BV(1); if( pdata.b2 ) PORTB |= _BV(2); else PORTB &=~_BV(2); if( pdata.b3 ) PORTB |= _BV(3); else PORTB &=~_BV(3); } return bytesRemaining ==0;/* 0 означает, что есть еще данные */ } /* ------------------------------------------------------------------------- */ usbMsgLen_t usbFunctionSetup(uchar data[8]) { usbRequest_t *rq =(void*)data; if((rq->bmRequestType & USBRQ_TYPE_MASK)== USBRQ_TYPE_CLASS){ /* HID устройство */ if(rq->bRequest == USBRQ_HID_GET_REPORT){ /* wValue: ReportType (highbyte), ReportID (lowbyte) */ // у нас только одна разновидность репорта, можем игнорировать report-ID bytesRemaining =sizeof(struct dataexchange_t); currentAddress =0; return USB_NO_MSG; // используем usbFunctionRead() для отправки данных хосту }elseif(rq->bRequest == USBRQ_HID_SET_REPORT){ // у нас только одна разновидность репорта, можем игнорировать report-ID bytesRemaining =sizeof(struct dataexchange_t); currentAddress =0; return USB_NO_MSG; // используем usbFunctionWrite() для получения данных от хоста } }else{ /* остальные запросы мы просто игнорируем */ } return0; } /* ------------------------------------------------------------------------- */ int main(void) { DDRB =0b00001110; // PB1,PB2,PB3 - выход usbInit(); usbDeviceDisconnect(); // принудительно отключаемся от хоста, так делать можно только при выключенных прерываниях! uchar i =0; while(--i){ // пауза > 250 ms _delay_ms(1); } usbDeviceConnect(); // подключаемся sei(); // разрешаем прерывания for(;;){ // главный цикл программы usbPoll(); // эту функцию надо регулярно вызывать с главного цикла, максимальная задержка между вызовами - 50 ms } return0; }
Компилируем, заливаем прошивку в микроконтроллер, подключаем к USB порту компьютера и вуаля:
Далее нам необходимо откомпилировать программу и записать ее в микроконтроллер. Если все настроено и сделано правильно то после подключения микроконтроллера к USB, компьютер должен определить ваше устройстов как HID.
Программы писали в среде C++ Builder 6, интерфейс программы создавался в визуальном редакторе. В форму добавил 3 чекбокса и две кнопки.
После создания проекта нам необходимо скопировать туда специальную библиотеку для работы с устройствами HID из среды C++ Builder, называется она hidlibrary.h сделана как класс. Эта библиотека при помощи LoadLibrary-WINAPI функции, подгружает hid.dll, далее при помощи GetProcAddress - происходит вычисление адреса функции внутри hid.dll.
Предоставляется удобный и простой интерфейс для работы с HID. Полезая стать "общение с контроллером по USB". На настройку проекта и передачу данных ушло довольно много времени, пока не наткнулся на эту библиотеку.
#define uchar unsigned char
#include <vcl.h>
#pragma hdrstop
#include "main.h"
#include "hidlibrary.h" // Библиотека для работы с Hid устройствами
#include "../Hid_example_firmware/usbconfig.h" // Здесь пишем путь к usbconfig.h
char vendorName[] ={USB_CFG_VENDOR_NAME,0};// для того что бы знать как
char productName[]={USB_CFG_DEVICE_NAME,0};// называется наше устройство
struct dataexchange_t // Описание структуры для передачи данных
{
uchar b1; // Я решил для примера написать структуру на 3 байта.
uchar b2; // На каждый байт подцепим ногу из PORTB. Конечно это
uchar b3; // не рационально (всего то 3 бита нужно).
}; // Но в целях демонстрации в самый раз.
// Для наглядности прикрутить по светодиоду и созерцать :)
struct dataexchange_t pdata ={0,0,0};
HIDLibrary<dataexchange_t> hid;// создаем экземпляр класса с типом нашей структуры
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent*Owner)
:TForm(Owner)
{
}
//---------------------------------------------------------------------------
int connect() // этой функцией будем подключаться к устройству
{
int i, n, res=0;
string exampleDeviceName ="";
exampleDeviceName += vendorName;
exampleDeviceName +=" ";
exampleDeviceName += productName;
n = hid.EnumerateHIDDevices();// узнаем все Hid устройства vid_16c0&pid_05df
// vid и pid указаны в hidlibrary.h константой idstring
for(i=0; i<n; i++) // ищем среди них наше
{
hid.Connect(i);
// GetConnectedDeviceName() возвращает string,
// где через пробел указаны vendor и product Name.
// Сравниваем, если совпало - значить устройство наше
if( hid.GetConnectedDeviceName()== exampleDeviceName )
{
res =1;
break;
}
}
return res;
}
//---------------------------------------------------------------------------
// Кнопка "Принять данные"
void __fastcall TForm1::Button1Click(TObject*Sender)
{
if(1== connect())
{
hid.ReceiveData(&pdata); // Читаем данные с устройства
if(pdata.b1)
CheckBox1->Checked=true;
else
CheckBox1->Checked=false;
if(pdata.b2)
CheckBox2->Checked=true;
else
CheckBox2->Checked=false;
if(pdata.b3)
CheckBox3->Checked=true;
else
CheckBox3->Checked=false;
}
else
{
AnsiString s ="";
s += vendorName;
s +=" ";
s += productName;
s +=" не подключено.";
ShowMessage(s);
}
}
//---------------------------------------------------------------------------
// Кнопка "Отправить данные"
void __fastcall TForm1::Button2Click(TObject*Sender)
{
if(1== connect())
{
if(CheckBox1->Checked)
pdata.b1 =1;
else
pdata.b1 =0;
if(CheckBox2->Checked)
pdata.b2 =1;
else
pdata.b2 =0;
if(CheckBox3->Checked)
pdata.b3 =1;
else
pdata.b3 =0;
hid.SendData(&pdata); // Отправляем данные устройству
}
else
{
AnsiString s ="";
s += vendorName;
s +=" ";
s += productName;
s +=" не подключено.";
ShowMessage(s);
}
}
В коде ничего сложного нет, есть функция connect() она необходима для подключения устройства, также обработчики событий для отправки данных и для принятия данных. Используя эти кнопочки можно теперь мигать светодиодами, включать и выключать нагрузки. Данные передаются при помощи структуры dataexchange_t, для микроконтроллера описана точно такая же структура.
Для того чтобы написанная нами прогрмма работа и на других компбютерах где не установлена программа Borland C++, нам нужно перейти настройки проекта и там выключить использование динамических библиотек. Для этого нужно выполнить следующее: Project>Options. Во вкладках Linker и Packages снять галочки напротив "Use dynamic RTL" и "Build with runtime packages.
Оригинал статьи: AVR и USB
Комментарии