Proteus и два с половиной hello world для UART и USB на микроконтроллере

от автора

Недавно разгребал папки на Dropbox и наткнулся на свои первые поделки. Когда делал первые шаги с микроконтроллерами, почти сразу меня стали посещать мысли и идеи об управлении моими поделками с ПК, либо каким-либо образом с этим самым ПК общаться. Это казалось увлекательным и «серьезным». Сейчас я стараюсь все сразу тестировать в железе, но в начале наступать на грабли и стрелять себе в ногу хотелось безболезненно и быстро. В этом мне неизменно помогал Proteus. Десятки раз пересобирая проекты было важно, чтобы можно было экспериментировать с комфортом, если так можно сказать. Это потом я уже купил фирменную отладку и что в Proteus, что в железе — все стало одинаково быстро.
В первую очередь мне хотелось попробовать 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МГц.

Под спойлером объяснение, как работает задающий генератор (в случае с PIC)

Все подробно описано тут

image

Дескриптор

Мне было очень полезно просто пробежаться по нему глазами и поэкспериментировать. Сразу стало ясно, куда копать в бесконечных 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *