Прием и передача данных по USB

Для отладки USB-COM написал программку на C#. Она открывает порт, читает данные и, при нажатии кнопки, отправляет данные. Теперь контроллер. Здесь пришлось знатно вылизать код, чтобы потом к этому не возвращаться. Приведу основную часть кода:

Здесь идут инклюды. Вобщем ничего особого. Для функции конвертации из int в char[] мне понадобилась stdlib.

#include "USB/usb.h"
#include "USB/HardwareProfile.h"
#include "USB/usb_function_cdc.h"
#include "delays.h"
#include "USB/usb_config.h"
#include "stdlib.h"

Вот здесь пока не совсем я понял. Объявляем 2 переменные в области UDATA. Если это объявление закоментить - ничего не работает... Видимо что-то не туда попадает. Позже присмотрюсь.

        #pragma udata
        INT16 ReceivedDataBuffer[CDC_DATA_IN_EP_SIZE];// Holds Data received from Host
        USB_HANDLE USBRxHandle = 0;

Объявление функций. Кучу функций инициализации я свел в одну - InitAll

        /** P R I V A T E  P R O T O T Y P E S ***************************************/
        void ProcessIO(void);
        void InitAll(void);
        void USBDeviceTasks(void);
        void YourHighPriorityISRCode();
        void YourLowPriorityISRCode();
        void USBCBSendResume(void);

Это указание на то, где находятся наши прерывания. Такое описание нужно для тех, кто пользуется bootloader - отладчики и тп. Для меня не нужно, адреса стандартные, но можно и так оставить.

    #define REMAPPED_RESET_VECTOR_ADDRESS        0x00
    #define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS    0x08
    #define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS    0x18

Дальше идет описание прерываний. Хотя все это я мог бы уместить в 1 строку, оставляю пока так.

        #pragma code REMAPPED_HIGH_INTERRUPT_VECTOR = REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS
    void Remapped_High_ISR (void)
    {
         _asm goto YourHighPriorityISRCode _endasm
    }
    #pragma code REMAPPED_LOW_INTERRUPT_VECTOR = REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS
    void Remapped_Low_ISR (void)
    {
         _asm goto YourLowPriorityISRCode _endasm
    }
    #pragma code
    #pragma interrupt YourHighPriorityISRCode
    void YourHighPriorityISRCode()
    {
            #if defined(USB_INTERRUPT)
                    USBDeviceTasks();
            #endif
    }
    #pragma interruptlow YourLowPriorityISRCode
    void YourLowPriorityISRCode()
    {
    }

Вот и начало основного......... мне нужен счетчик Z, поэтому он объявлен здесь. Потом, я полагаю, он не понадобится и я его вытру.

#pragma code
unsigned int z = 0;
void main(void)
{
    InitAll();        // Это настройка портов и включение USB
    while(1)
    {
        ProcessIO();    // Это выполнение наших задач
    }
}

void InitAll(void)
{
    int i = 0;
    int k = 0;
    ANSEL = 0;            // Все порты - цифровые.
    TRISCbits.RC3 = 0; // лед на С3
    TRISCbits.RC6 = 0; // лед на С6
    LATCbits.LATC3 = 0; // исходное состояние в 0
    LATCbits.LATC6 = 0; // ...

Дальше моргаем, чтобы обозначить запуск контроллера. 20 раз по 20'000 пустых операций - доли секунды.

    for(i=0;i<20;i++)
    {
        for(k = 0;k<20000;k++)
        {
        }
        LATCbits.LATC6 = ~LATCbits.LATC6;
    }

Включаем USB

    USBDeviceInit();
    USBDeviceAttach();
}

Теперь самое интересное. Описывал код на английском, чтобы все было на одном языке - и исходник и комменты


void ProcessIO(void)
{
    char numBytes;                                      // Received buffer length
    char buffer[64];                                    // Buffer for receive USB data
    if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) // If USB is not connected
    {
        LATCbits.LATC3 = 1;                             // Turn on LED on RC3
        return;                                         // Nothing else to do
    }
    LATCbits.LATC3 = 0;                                 // If connected, turn on LED on RC3
    numBytes = getsUSBUSART(buffer,sizeof(buffer));     // Receive data from Host
    if(numBytes \> 0)                                   // If there is some data...
    {
        if(USBUSARTIsTxTrfReady())
        {
            int length = (int)numBytes;                 // Lenght of received buffer
            char data[] = "PIC received bytes: ";       // Buffer containing string
            char len[10];                               // Buffer containing length as string
            itoa(length, len);                          // Converting INT to String
            strcat(data,len);                           // Copy length as string to existing string
            putsUSBUSART(data);                         // Send string to host
        }
    }
    z++;                                                // Count Z
    if(z == 40000)                                      // If counted to 40'000
    {
        z=0;                                            // Clear counter
        LATCbits.LATC6 = ~LATCbits.LATC6;               // Switch led on RC3
        if(USBUSARTIsTxTrfReady())                      // If USB is ready to send
        {
            char data[] = ".";                          // We will send '.'
            putsUSBUSART(data);                         // Send alive '.' to host
            //CDCTxService();                             // Process Tx to host at last
        }
    }
    CDCTxService();                                     // Process Tx to host at last
}

Вобщем это делает следующее:
1. Проверяем подключение. Если есть - идем дальше. Если нет - включаем лед на С3 и выходим совсем.
2. Получаем данные из USB. Если есть - обрабатываем, нет - идем дальше
3. Обработка полученных данных - смотрим размер полученных данных и отправляем его обратно на хост со строкой префиксом, чтобы в программе отладчике симпатично смотрелось
4. Счетчик наш считает до 40'000. Если не насчитал - проходим мимо. Если насчитал - делаем некоторые вещи.
5. По достижении 40000 переключаем состояние леда на С6 для визуального контроля и отправляем через USB знак ".", чтобы в отладчике на ПК было видно, что контроллер что-то посылает.
6. В итоге вызываем главную функцию для начала передачи данных по USB:     CDCTxService()
Эта функция инициализирует передачу данных по USB на хост (ПК).

В итоге в своей отладочной программе я вижу следующее:

Порт открыт
..........Передано: 1
PIC received bytes: 1
.........Передано: 1234
PIC received bytes: 4
........
Порт закрыт

То есть: открывается порт, принимаем точки от контроллера, потом посылаем 1 байт на контроллер, на что он отвечат, что принят байт, потом опять точки и тп.

Вот. Опять 3 часа ночи... Результатом доволен, результат стабильный.

Далее по плану у меня следующее:
1. Запустить память SPI
2. Запись и чтение блоков из программы на C# в память SPI.
3. Отработка "каналов"...

Ну а на сегодня хватить.

PIC18F13K50 USB

Вобщем все заработало. Странно, но заработало. Теперь по пунктам.

Железо:
1. Берем PIC18F13K50
2. Берем кристал на 12 МГц и конденсаторы к нему 22 пФ
3. Ноги D+ и D- подключаем к соответствующим ногам на разъеме USB
4. USB GND подключаем к GND нашей схемы.
5. USB Vcc я подключил к VCC своей схемы через резистор 10 КОм (может быть это и не нужно)

Программно:
1. Берем MPLAB IDE
2. Делаем проект новый для контроллера нашего.
3. Устанавливаем самый последний стэк от Microchip
4. Берем пример cdc serial emulation и допиливаем напильником.
5. Конфигурируем чип.
6. Прошиваем.
7. Ставим дрова от Microchip для CDC
8. Втыкаем наше устройство в PC, проверяем, что появился COM порт.

Теперь о подводных хренях.

1. Кристал на 12 МГц можно использовать для работы USB в режиме FULL_SPEED. Для этого делаем так в конфигурации:
        #pragma config CPUDIV = NOCLKDIV // ЦПУ наш работает без делителя
        #pragma config USBDIV = OFF // USB работает без делителя
        #pragma config FOSC   = HS // Кристалл работает в режиме HS. Можно поэкспериментировать, может можно и в других.
        #pragma config PLLEN  = ON // Умножаем тактовую частоту на 4. Получаем желаемые 48 МГц.

2. В схемотехнике не нужно использовать подтягивающие резисторы для линий D+, D-. Все что нужно - использовать опцию #define USB_PULLUP_OPTION USB_PULLUP_ENABLE в файле usb_config.h

3. В режиме LOW_SPEED действуют особые ограничения на размеры буферов, используемых стеком USB. Я не смог запустить свое устройство в LOW_SPEED, хотя перебрал, перечитал все об этих буферах. Мне не важно, поэтому я на это пока забил. Пусть работает в HIGH_SPEED.

4. Для упрощения работы в проекте, я скинул все нужные файлы из установочного комплекта microchip в подпапку проекта.
Получилось, что нужные header файлы лежат в подпапке USB: Compiler.h GenericTypeDefs.h HardwareProfile.h usb.h usb_ch9.h usb_common.h usb_config.h usb_device.h usb_device_local.h usb_function_cdc.h usb_hal.h usb_hal_local.h usb_hal_pic18.h usb_host.h
Файлы, исходников: main.c pic_config.c usb_descriptors.c usb_device.c usb_function_cdc.c
Чтобы компилятор не ругался, пришлось поправить везде #include на нужные пути. Зато теперь весь проект в одном месте и никуда не лазит за дополнительными файликами.

5. Для диагностирования состояния подключенного к ПК устройства, я использовал USBlyzer. Триальная версия на 30 дней мне подходит, а там дальше видно будет.

На всякий случай сохраню тут HEX файл, чтобы можно было контроллер прошить сходу, без всего вышеописанного и проверить работоспособность платки.  [опаньки, а в жж незя файлы сразу крепить к посту(((( ] Ладно, позже пришью.

PS: На самом деле этот маленький пост - результат жуткой ебанины на протяжении нескольких суток.

Подключение по USB

Чтобы контроллер сразу был виден как USB устройство понадобилось приделать 1,5 КОм между Vcc и D-
До этого компьютер не видел вообще контроллер.

Видеорегистратор Polyvision PVDR-1665

Мы устанавливаем системы видеонаблюдения. Нашему клиенту поставили PVDR-1665 c жестким диском Seagate ST1000DL002.Прикол в том, что скорости этого винта не хватает для записи всех каналов. Пришлось сегодня менять на
Жесткий диск 1000ГБ Western Digital "WD RE4 WD1003FBYX" 7200об./мин., 64МБ (SATA II)

Это по-сути WD Caviar Black. Очень интересно - сколько это сцуко проживет.

Конкретный облом с XC8 и USB Framework

Выяснилось, что USB Framework microchip не работает с XC8... Зарегистрировался на форуме (mswh). и !!!!! Нашел сообщение на эту тему, размещенное 1 день назад!!! Вот оно:

http://www.microchip.com/forums/m679588.aspx

Пока разбираюсь - может все же есть шанс запустить UF под XC8. Параллельно начал все тоже самое писать под C18.

Прикол с последовательностью include

Была последовательность:
#include <stdio.h>
#include <stdlib.h>
#include "htc.h"
#include "delays.h"
#include <xc.h>

Добавил:
#include "USB/usb.h"

Начал выдавать ошибку:
(908) exit status = 1
C:\Program Files (x86)\Microchip\xc8\v1.10\include\stdlib.h:106: error: type redeclared
make[2]: *** [build/default/production/newmain.p1] Error 1
C:\Program Files (x86)\Microchip\xc8\v1.10\include\stdlib.h:106: error: conflicting declarations for variable "ltoa" (C:\Program Files (x86)\Microchip\xc8\v1.10\include\stdlib.h:109)
make[1]: *** [.build-conf] Error 2

Исправилось отключением stdlib. Правильный заголовок:

#include <xc.h>
#include <stdio.h>
//#include <stdlib.h>
#include "htc.h"
#include "delays.h"
#include "USB/usb.h"

Ошибки

Схема прерываний по USB вобщем-то не сложная. Выяснилось, что я перепутал ноги D+ и D- Теперь надо перепилить дорожки и навесным способом их переделать.
usb

А тем временем в коде это выглядит так:
void interrupt low_priority Interrupt(void)
{
    if(UIRbits.ACTVIF == 1) // Bus Activity Detect Interrupt bit
    {
        BlinkA5();
        UIRbits.ACTVIF=0;
    }

    if(UIRbits.IDLEIF == 1) // Idle Detect Interrupt bit
    {
        BlinkC3();
        UIRbits.IDLEIF = 0;
    }

    if(UIRbits.SOFIF == 1) // Start-of-Frame Token Interrupt bit
    {
        BlinkC6();
        UIRbits.SOFIF = 0;
    }
}

int main(int argc, char** argv)
{
    GIE = 1;
    USBIE = 1; // Глобальное включение прерываний USB
}

После перепайки, проверю, что из этого срабатывает. Потом перепишу код, посмотрю какие еще прерывания срабатывают при подключении кабеля USB.

В теории сразу станет понятно, какие прерывания отрабатывают и почему.

После этого можно будет пробовать посылать и принимать данные.

Прерывания

На ноге С0 у меня вход, который должен включать прерывание. С0 это INT0 по доке на чип. Соответственно в программе делаю так в main():
    GIE = 1;        // Походу глобальное включение прерываний
    INT0E = 1;    // Включение прерывания INT0.

Добавляю перед main() функцию обработки прерывания:
void interrupt low_priority Interrupt(void)        // Моя функция называется Interrupt, она по вектору низкого приоритета
{
    if(INT0F == 1)    // Если прерывание INT0 выставило бит срабатывания INT0F
    {
        BlinkA5();      // Моргаем ледом
        INT0F = 0;    // Вычищаем бит срабатывания прерывания
    }
}

Подъебака - фронт, по которому срабатывает прерывание. По умолчанию на падающем фронте. Можно сделать на восходящем.