В первую очередь мне хотелось попробовать UART и USB, а потом уже и Ethernet. Каждому желанию я придумывал свой «проект». Многие идеи так и остались в виде проекта для Proteus — идея надоедала сразу после реализации программной части.
Надеюсь, данный пост поможет всем, кто хотел попробовать сделать свое USB устройство или просто увидеть, что это все не так сложно; более того, мне хотелось, чтобы можно было попробовать сразу в симуляторе. Рассказать как новичок для новичка — чтобы захотелось открыть гугл и начать читать, чтобы сделать больше, узнать лучше.
Я не буду рассказывать о регистрах, режимах. Я сомневаюсь, что это поможет зажечь огонь в глазах. Возможно, кто-то захочет сделать что-то полезное для себя и у простых примеров больше шансов затянуть в это крайне увлекательное творчество (а для меня уже практически наркотик). А прежде чем бежать на ebay или начинать считать дни с момента отправки отладочной платы из Китая, можно попробовать свои силы в виртуальном микроконтроллере.
Я хотел бы попробовать сделать два своеобразных hello world проекта, которые, тем ни менее, не сильно сложнее стандартного LED blinking. Под катом много изображений.
Описание и примеры кода в тексте будут для микроконтроллера PIC18F4550, уж прошу простить меня, с atmel я не работал. А STM8/32 отсутствуют даже в 8-й версии.
Код для микроконтроллеров написан для компилятора mikroC от Mikroelektronika и писался для удобства понимания, надеюсь, у меня это вышло хотя бы частично.
Протеус и обвязка MCU
Протеус, как мне кажется, создан для макетирования и тестирования работы софта в первую очередь. Возможно именно поэтому он допускает множественные упрощения в создании схем. Ниже приведен абсолютно достаточный набор для тестирования общения с ПК по UART/USB. Хочу снова обратить внимание, что такая схема работать будет только в Proteus’е.
Все проекты приложены к статье, так что установив программу, можно сразу все попробовать.
U(S)ART
Тема избита, но все же позволю себе описать ее еще раз.
Чтобы подключить к нашему виртуальному микроконтроллеру putty или любую другю произвольную программу, нам нужно сделать несколько действий.
Модуль COMPIM использует виртуальный порт, чтобы к нему подключиться, нужно создать ещё один и соединить их как нуль модем.
Хорошим помощником тут будет бесплатная утилита com0com.
Там создаем два виртуальных порта, в моем случае это COM 3 и 4. Один подключаем к Proteus’овскому COMPIM, второй уже используем «снаружи»
Окно программы
А вот так это будет выглядеть в devmgmt.msc (Диспетчере устройств)
Теперь все готово к тесту.
char uart_rd; void main() { UART1_Init(9600); UART1_Write_Text("Hello Habrahabr"); UART1_Write(10); UART1_Write(13); for(;;) { if(UART1_Data_Ready()) { uart_rd = UART1_Read( ); switch(uart_rd) { case 0xD: UART1_Write(10); UART1_Write(13); break; default: UART1_Write(uart_rd); break; } } } }
Настроим COMPIM устройство (правый клик — Edit properties).
Напомню, что у меня com0com эмулирует порты COM3/COM4
На всякий случай, приложу картинку с настройками микроконтроллера
Включаем Putty, переводим его в режим Serial и подключаем к COM4. Теперь… нажми на кнопку — получишь результат
Печатаем текст в окне терминала и микроконтроллер делает echo нам назад. По нажатию enter переводим каретку + новая строка.
Backspace так же работает. Сразу вспомнилось как чатился с друзьями по Hyper Terminal, часами занимая телефонную линию…
Теперь можно изменяя код поиграться с микроконтроллером.
#define CRLF UART1_Write(10);UART1_Write(13) char uart_rd = 0; char cmd[32] = {0}; int char_counter = 0; void cmd_exec(void) { if (strstr(cmd, "hello")) { UART1_Write_Text("EHLO"); CRLF; } else if (strstr(cmd, "test")) { UART1_Write_Text("TSET"); CRLF; } else { UART1_Write_Text("Unknown command"); CRLF; } char_counter=0; memset(cmd, 0, sizeof cmd); } void main(void) { UART1_Init(9600); UART1_Write_Text("MCU started"); UART1_Write(10); UART1_Write(13); for(;;) { if(UART1_Data_Ready()) { uart_rd = UART1_Read( ); switch(uart_rd) { case 0xD: CRLF; cmd_exec(); break; default: UART1_Write(uart_rd); break; } cmd[char_counter++] = uart_rd; if(char_counter == (sizeof cmd - 1)) { CRLF; cmd_exec(); } } } }
USB HID
Сделать свое USB устройство оказалось делом непростым для меня тогда. Тестировать мы будем HID устройства, то есть, в большинстве случаев — устройства ввода.
Очень хотелось мне сделать автоматический ввод пароля, а так же блокировку компьютера, когда я отошел, и разблокировку, когда я подошел. Ну и кучу чего еще, что можно было реализовать посредством виртуальной клавиатуры.
Кучу полезной информации по HID можно найти тут. Читать — не перечитать.
Вкратце: каждое USB HID устройство имеет специальное описание, дескриптор. Которое описывает что это за устройство, как им можно управлять, сколько оно потребляет от шины или же имеет самостоятельное питание и кучу другой информации. Поэтому нам нужно сделать правильное описание, чтобы ОС могла понять, что это клавиатура и могла с ней работать.
Но сначала, чтобы Proteus мог пробросить свой виртуальный USB хост в наш реальный ПК, необходимо поставить виртуальный драйвер, он идет в комплекте
Чтобы устройство работало как полноценное FullSpeed USB 2.0, необходимо включить PLL и настроить его соответствующим образом.
В Proteus’е так же надо выставить частоту процессора как 96МГц.
Дескриптор
Мне было очень полезно просто пробежаться по нему глазами и поэкспериментировать. Сразу стало ясно, куда копать в бесконечных PDF с usb.org
В нашем случае от «стандартного» декриптора изменений немного:
Мы изменили VID/PID, указали, что у нас буфер ввода/вывода на 8 байт каждый и, собственно, указали, что у нас устройство класса «клавиатура» и использовать его нужно именно так.
Все названия переменных говорят сами за себя.
const unsigned int USB_VENDOR_ID = 0xdead; const unsigned int USB_PRODUCT_ID = 0xbeaf; const char USB_SELF_POWER = 0x80; // Self powered 0xC0, 0x80 bus powered const char USB_MAX_POWER = 50; // Bus power required in units of 2 mA const char HID_INPUT_REPORT_BYTES = 8; const char HID_OUTPUT_REPORT_BYTES = 8; const char USB_TRANSFER_TYPE = 0x03; //0x03 Interrupt const char EP_IN_INTERVAL = 1; const char EP_OUT_INTERVAL = 1; const char USB_INTERRUPT = 1; const char USB_HID_EP = 1; const char USB_HID_RPT_SIZE = 63; /* Device Descriptor */ const struct { char bLength; // bLength - Descriptor size in bytes (12h) char bDescriptorType; // bDescriptorType - The constant DEVICE (01h) unsigned int bcdUSB; // bcdUSB - USB specification release number (BCD) char bDeviceClass; // bDeviceClass - Class Code char bDeviceSubClass; // bDeviceSubClass - Subclass code char bDeviceProtocol; // bDeviceProtocol - Protocol code char bMaxPacketSize0; // bMaxPacketSize0 - Maximum packet size for endpoint 0 unsigned int idVendor; // idVendor - Vendor ID unsigned int idProduct; // idProduct - Product ID unsigned int bcdDevice; // bcdDevice - Device release number (BCD) char iManufacturer; // iManufacturer - Index of string descriptor for the manufacturer char iProduct; // iProduct - Index of string descriptor for the product. char iSerialNumber; // iSerialNumber - Index of string descriptor for the serial number. char bNumConfigurations; // bNumConfigurations - Number of possible configurations } device_dsc = { 0x12, // bLength 0x01, // bDescriptorType 0x0200, // bcdUSB 0x00, // bDeviceClass 0x00, // bDeviceSubClass 0x00, // bDeviceProtocol 8, // bMaxPacketSize0 USB_VENDOR_ID, // idVendor USB_PRODUCT_ID, // idProduct 0x0001, // bcdDevice 0x01, // iManufacturer 0x02, // iProduct 0x00, // iSerialNumber 0x01 // bNumConfigurations } ; /* Configuration 1 Descriptor */ const char configDescriptor1[]= { // Configuration Descriptor 0x09, // bLength - Descriptor size in bytes 0x02, // bDescriptorType - The constant CONFIGURATION (02h) 0x29,0x00, // wTotalLength - The number of bytes in the configuration descriptor and all of its subordinate descriptors 1, // bNumInterfaces - Number of interfaces in the configuration 1, // bConfigurationValue - Identifier for Set Configuration and Get Configuration requests 0, // iConfiguration - Index of string descriptor for the configuration USB_SELF_POWER, // bmAttributes - Self/bus power and remote wakeup settings USB_MAX_POWER, // bMaxPower - Bus power required in units of 2 mA // Interface Descriptor 0x09, // bLength - Descriptor size in bytes (09h) 0x04, // bDescriptorType - The constant Interface (04h) 0, // bInterfaceNumber - Number identifying this interface 0, // bAlternateSetting - A number that identifies a descriptor with alternate settings for this bInterfaceNumber. 2, // bNumEndpoint - Number of endpoints supported not counting endpoint zero 0x03, // bInterfaceClass - Class code 0, // bInterfaceSubclass - Subclass code 0, // bInterfaceProtocol - Protocol code 0, // iInterface - Interface string index // HID Class-Specific Descriptor 0x09, // bLength - Descriptor size in bytes. 0x21, // bDescriptorType - This descriptor's type: 21h to indicate the HID class. 0x01,0x01, // bcdHID - HID specification release number (BCD). 0x00, // bCountryCode - Numeric expression identifying the country for localized hardware (BCD) or 00h. 1, // bNumDescriptors - Number of subordinate report and physical descriptors. 0x22, // bDescriptorType - The type of a class-specific descriptor that follows USB_HID_RPT_SIZE,0x00, // wDescriptorLength - Total length of the descriptor identified above. // Endpoint Descriptor 0x07, // bLength - Descriptor size in bytes (07h) 0x05, // bDescriptorType - The constant Endpoint (05h) USB_HID_EP | 0x80, // bEndpointAddress - Endpoint number and direction USB_TRANSFER_TYPE, // bmAttributes - Transfer type and supplementary information 0x40,0x00, // wMaxPacketSize - Maximum packet size supported EP_IN_INTERVAL, // bInterval - Service interval or NAK rate // Endpoint Descriptor 0x07, // bLength - Descriptor size in bytes (07h) 0x05, // bDescriptorType - The constant Endpoint (05h) USB_HID_EP, // bEndpointAddress - Endpoint number and direction USB_TRANSFER_TYPE, // bmAttributes - Transfer type and supplementary information 0x40,0x00, // wMaxPacketSize - Maximum packet size supported EP_OUT_INTERVAL // bInterval - Service interval or NAK rate } ; const struct { char report[]; } hid_rpt_desc = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0xe0, // USAGE_MINIMUM 224(Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM 231(Keyboard Right GUI) (left and right: alt, shift, ctrl and win) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x05, // REPORT_COUNT (5) 0x75, 0x01, // REPORT_SIZE (1) 0x05, 0x08, // USAGE_PAGE (LEDs) 0x19, 0x01, // USAGE_MINIMUM (Num Lock) 0x29, 0x05, // USAGE_MAXIMUM (Kana) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x03, // REPORT_SIZE (3) 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x65, // LOGICAL_MAXIMUM (101) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xc0 // END_COLLECTION } ; //Language code string descriptor const struct { char bLength; char bDscType; unsigned int string[1]; } strd1 = { 4, 0x03, { 0x0409 } } ; //Manufacturer string descriptor const struct{ char bLength; char bDscType; unsigned int string[10]; } strd2={ 22, //sizeof this descriptor string 0x03, { 'H','a','b','r','a','h','a','b','r' } } ; //Product string descriptor const struct{ char bLength; char bDscType; unsigned int string[15]; } strd3={ 32, //sizeof this descriptor string 0x03, { 'H','a','b','r','a','K','e','y','b','o','a','r','d' } } ; //Array of configuration descriptors const char* USB_config_dsc_ptr[1]; //Array of string descriptors const char* USB_string_dsc_ptr[3]; void USB_Init_Desc(){ USB_config_dsc_ptr[0] = &configDescriptor1; USB_string_dsc_ptr[0] = (const char*)&strd1; USB_string_dsc_ptr[1] = (const char*)&strd2; USB_string_dsc_ptr[2] = (const char*)&strd3; }
Этот дескриптор добавляется в проект, и будет автоматически использован при сборке.
unsigned short USBResponse[8] = { 0} absolute 0x500; unsigned short USBCommand[8] = {0} absolute 0x508; char *text="habrahabr"; int i = 0; void interrupt(void) { USB_Interrupt_Proc( ); } void clrUSB(void) { memset(USBCommand, 0, sizeof USBCommand); while ( !HID_Write(&USBCommand, sizeof USBCommand)); } void main(void) { ADCON1 |= 0x0F; CMCON |= 7; HID_Enable(&USBResponse, &USBCommand); delay_ms(1000); for (i=0; i < strlen(text); i++) { USBCommand[2] = text[i] - 93; while ( !HID_Write(&USBCommand, sizeof USBCommand)); clrUSB(); delay_ms(200); } }
По сути вся работа заключается в заполнении соответствующего буфера и в отравке его в ПК. Не сложнее, чем с UART. Вся работа выполняется в подпрограмме прерывания. В комплекте с IDE уже идут готовые библиотеки работы с HID.
Тут следует пояснить, что scan коды клавиатуры отличаются от ASCII, но чтобы не перегружать код (у нас же hello world), я обошел сие неудобство примитивным образом. Работать будет только для букв в нижнем регистре. Желающие могут сами сделать преобразование. Подобный девайс я использую для KVM На работе, беспроводной удлинитель клавиатуры — наши D-Linkовские KVM не хотят понимать USB донглы беспроводные.
Теперь открываем Notepad, запускаем Proteus (предварительно кликаем по виртуальному штекеру — USB разъем «вставится»), сразу переводим фокус мышкой на Notepad и наблюдаем, как наше творение печатает слово habrahabr.
А в диспетчере появилось наше устройство
Теперь можно на основе этого добавлять что-то свое.
Пару слов про то, как это работает.
Для ввода-вывода с клавиатуры зарезервировано 8 байт:
0 Модификатор
1 Не используется
2 Клавиша 1
3 Клавиша 2
4 Клавиша 3
5 Клавиша 4
6 Клавиша 5
7 Клавиша 6
Модификаторы, это спец клавиши Ctrl, Shift, Alt. Их можно объединять. Например, для комбинации Ctrl Alt Del:
Modifier: 0b00000101 Ctrl, Shift, Alt
Key code: 0x4c (клавиша Delete)
Следует помнить, что сразу после передачи данных, необходимо стирать буфер USB, иначе получится эффект залипшей клавиши. То есть отправлять на ПК восемь нуль байт. В примере это делает подпрограмма clearUSB
Подробнее про сканкоды описано в appnote от microchip
Точно так же можно создать обычное HID устройство и передавать/принимать байты с ПК и по логике работы это почти ничем не отличается от того же UART. Но это уже требует отдельной работы, например, с libusb со стороны ПК.
Ethernet
Не отражено в заголовке, но про это тоже стоит сказать.
К сожалению, данный пример не будет завершенным, поскольку это тема отдельного разговора.
Но, по крайней мере, я опишу как настроить сам симулятор, и кое-что попробовать все же получится.
Тут как раз пример того, что работает в железе, но не всегда работает в симуляторе. В Proteus реализованы VSM модели для микросхем ENC28J60 и RTL8019. Да-да, тот самый чип, который всем нам был знаком по бюджетным сетевым картам. Использование ENC описано достаточно широко и проблем тут быть не должно. Например, уважаемый DIHALT все давно и преподробнейше описал. Поэтому, чтобы не было скучно, возьмем 8019, тем более я пишу под неё софт для использования совместно с Z80.
Как и в случае с USB, нам надо установить драйверы, но теперь WinPCAP. Они лежат в папке Virtual Network, рядом с драйверами USB. Или скачать с сайта самую свежую версию
После этого у нас появится новая виртуальный сетевой интерфейс с адресом 192.168.95.1, который, разумеется, можно поменять.
Cделаем на UART отладочный интерфейс по уже известной нам схеме.
HINT: Если вас раздражают надписи TEXT — в Description компонента поставьте пробел
В свойствах микросхемы пропишем номер или IP нашей виртуальной сетевухи и можно поменять кое-какие настройки.
В моем случае это 192.168.50.1 (я изменил)
Далее дело за софтом. Полноценный драйвер для 8019 у меня пока не готов, да и это тема отдельной большой статьи, хотя вряд ли такой анахронизм кому-то интересен. Но даже без настройки протоколов (IP адрес у нас сейчас 0.0.0.0), так как я разрешил ICMP/Broadcast в регистрах, мы можем попинговать какой-нибудь левый адрес в нашей подсети, и сетевая карта микросхема радостно помигает нам светодиодом при получении пакета. После каждой попытки, используйте новый адрес, а то ARP таблица же кэшируется.
Вместе с проектами приложены готовые HEX, так что компилятор можно даже не качать, если нужно просто проверить, что все работает.
Чтобы симуляция работала — не забывайте указать микроконтроллеру где лежит ваш HEX файл.
Скачать все файлы можно по этой ссылке
Надеюсь, у кого-то загорится желание попробовать и реализовать в железе.
ссылка на оригинал статьи http://habrahabr.ru/post/206034/
Добавить комментарий