UFMG - ICEx DEPARTAMENTO DE CIÊNCIA DA UNIVERSIDADE FEDERAL DE MINAS GERAIS C O M P U T A Ç Ã O Um agente embutido para conversão de uma interface serial em USB NÚMERO Ana Luiza de Almeida Pereira Zuquim [email protected] MÊS ANO PUBLICAÇÃO Resumo Este documento tem como objetivo descrever o processo de desenvolvimento de um conversor de interface serial para USB – Universal Serial Bus – genérico, utilizando como exemplo de dispositivo o no-break da Engetron. USB é um novo padrão de conexão de periféricos criado com o objetivo de facilitar a conexão de periféricos e torná-la eficiente. Permite a conexão de até 127 dispositivos simultaneamente e reduz o custo para o usuário final. Foi criado em função da crescente demanda por interfaces de comunicação, uma vez que o número de dispositivos conectados ao microcomputador também cresceu muito, tornando o número de portas de comunicação existentes nos micros de hoje insuficiente para as aplicações existentes. Outras características, como velocidade de transmissão/recepção destas portas e dificuldade de configuração para o usuário final, estimularam a criação de um novo protocolo, mais fácil, rápido e eficiente. Apesar da especificação do protocolo USB trazer inúmeros benefícios, é necessário prover uma forma de se manter a compatibilidade com os equipamentos já existentes. Considerando que o protocolo serial é um dos protocolos mais utilizados nos dias de hoje, o projeto de um conversor de interface serial para USB é de grande valor prático. ÍNDICE RESUMO _________________________________________________________________________________ 2 1 INTRODUÇÃO _________________________________________________________________________ 4 2 USB – VISÃO GERAL ___________________________________________________________________ 5 2.1 2.2 2.2.1 2.3 2.4 CARACTERÍSTICAS FUNCIONAIS _________________________________________________________ CLASSES USB ________________________________________________________________________ HUMAN INTERFACE DEVICE CLASS (HID) _______________________________________________ TIPOS DE TRANSFERÊNCIAS _____________________________________________________________ REQUISIÇÕES ________________________________________________________________________ 5 7 7 8 9 3 ESPECIFICAÇÃO DO SISTEMA __________________________________________________________ 9 3.1 3.2 REQUERIMENTOS DO HOST _____________________________________________________________ 9 REQUERIMENTOS DO DISPOSITIVO ______________________________________________________ 10 4 DESCRIÇÃO DO HARDWARE ___________________________________________________________ 11 5 PROJETO E IMPLEMENTAÇÃO DO SOFTWARE _________________________________________ 12 5.1 5.2 5.3 5.4 DEFINIÇÃO DOS DESCRITORES __________________________________________________________ ROTINAS DE INTERRUPÇÃO NOS ENDPOINTS ______________________________________________ DETECÇÃO E ENUMERAÇÃO DO DISPOSITIVO ______________________________________________ O PROCESSO DE ENVIO E RECEPÇÃO DE DADOS ____________________________________________ 12 16 17 18 6 DRIVERS E APLICAÇÕES ______________________________________________________________ 18 7 CONCLUSÕES ________________________________________________________________________ 19 AGRADECIMENTOS _____________________________________________________________________ 19 REFERÊNCIAS BIBLIOGRÁFICAS_________________________________________________________ 19 ANEXO A ________________________________________________________________________________ 20 1 Introdução Este documento descreve a especificação e implementação de um conversor de interface serial para interface USB – Universal Serial Bus. O conversor é responsável por receber dados de uma interface serial de um periférico (ou dispositivo) e repassá-los à interface USB de um microcomputador, permitindo, da mesma forma, o envio de dados do PC para o dispositivo. Em função da crescente demanda por interfaces de comunicação, provocada por um número cada vez maior de periféricos conectados ao computador, o número de portas de comunicação disponíveis nos micros de hoje tornou-se insuficiente para a gama de aplicações existentes. Além disso, características como velocidade de transmissão/recepção das portas existentes e dificuldades associadas ao custo, configuração e conexão de periféricos estimularam, ainda mais, a criação de um novo protocolo que simplificasse todo o processo do ponto de vista do usuário final e que fosse mais rápido e eficiente. USB surgiu como um novo padrão de conexão de periféricos desenvolvido por líderes da indústria de computadores e telecomunicações1 a partir da necessidade de se integrar de uma melhor forma estes dois ramos que cresceram separadamente e que, nos dias de hoje, convergem para um mesmo objetivo. O padrão USB utiliza a tecnologia Plug and Play, onde um dispositivo é conectado e automaticamente reconhecido. Permite ainda conexão dinâmica (hot attachment), onde um dispositivo pode ser conectado com o computador ligado, não sendo necessário reinicializar a máquina. Através de uma porta USB é possível conectar até 127 dispositivos simultaneamente em um computador, resolvendo diversos problemas de conflito de recursos (DMA’s, IRQ’s, jumpers, etc.). USB apresenta-se, portanto, como um protocolo que regulamenta meios físicos, software do host, firmware dos dispositivos, plugs e hubs para conexão de periféricos a computadores [8]. Implica, ainda, em aumento de performance com baixo consumo, resultando em baixo custo. Se adequam a essa nova tecnologia periféricos de baixa e média velocidades, como monitores, mouses, dispositivos de E/S de áudio, telefones, modems, teclados, impressoras, entre outros [2,3]. Essa tecnologia já está sendo implementada e comercializada amplamente, estando presente em PCs e periféricos. O projeto de um conversor de interface serial para USB permite que um dispositivo com interface serial se comunique através de uma interface USB, liberando a interface serial do micro para outras aplicações. Desta forma, não estaremos mais restritos à disponibilidade de uma porta de comunicação serial, além de podermos usufruir das vantagens do protocolo USB. A utilização de um conversor de interfaces permite ainda que mantenhamos o hardware e software do dispositivo utilizados inalterados, passando para o conversor a responsabilidade de lidar com as diferenças entre os protocolos de comunicação. O projeto foi desenvolvido baseado nos no-breaks Engetron2, que podem ser gerenciados através da interface serial para troca de dados com o PC. Esta comunicação com os no-breaks é feita, muitas vezes, periodicamente, uma vez que a utilização da porta de comunicação não pode ficar restrita a um único periférico. Para o desenvolvimento do projeto tornou-se necessário um estudo prévio do padrão USB, de sua interação com drivers e aplicativos, além de uma análise das arquiteturas de processadores com interface USB existentes no mercado. O desenvolvimento de um dispositivo USB envolve a implementação do periférico propriamente dito além do desenvolvimento de um software que execute no PC e que se comunique com o mesmo. 1 2 Empresas fundadoras do USB Forum: Compaq, IBM, Intel, Microsoft, DEC, NEC e Northern Telecom. Engetron é uma empresa conceituada especializada no desenvolvimento de no-breaks inteligentes (smart UPSs) Para melhor entendimento do texto, serão introduzidos conceitos importantes do padrão USB, tornando assim mais claras as decisões de projeto. Uma breve descrição da família de microcontroladores utilizado permitirá ainda uma contextualização da estrutura exposta na especificação em relação ao hardware e firmware implementado. As estruturas de dados e decisões de projeto serão explicitadas no texto, assim como as etapas do desenvolvimento, possibilitando ao leitor uma fácil adaptação do código do conversor para outros periféricos. 2 USB – Visão Geral A especificação USB [1] descreve os atributos do barramento, define o protocolo, tipos de transações, gerenciamento do barramento e programação da interface, operações estas requeridas no processo de desenho e implementação de sistemas e periféricos compatíveis ao padrão USB. No protocolo USB, o barramento toma para si a responsabilidade de instalar drivers e reconfigurar automaticamente o sistema quando da inserção ou remoção de um dispositivo. Padroniza ainda dois tipos de conectores diferentes, permitindo a transmissão bidirecional e evitando confusões nas conexões. O primeiro tipo é utilizado para conexão ao microcomputador, e é único para todo tipo de periférico. O segundo tipo é utilizado para se conectar o cabo ao periférico, em casos onde a utilização de um cabo fixo é impraticável. A conexão de dois ou mais periféricos é conseguida através da utilização de hubs, que podem ser implementados como dispositivos independentes ou embutidos em periféricos como monitores, teclados, etc. Os dispositivos compartilham a largura de banda utilizando um protocolo baseado em tokens e comandado pelo host. 2.1 Características Funcionais USB se comporta como um barramento Master/Slave onde o Master é o USB Host, que toma conhecimento da inserção e remoção dos periféricos, inicia o processo de enumeração e comanda todas as transações subsequentes nele. É também de sua responsabilidade coletar o status e as estatísticas de cada periférico. Os periféricos são Slaves do barramento, podendo ser funcionais (teclado, mouse, joystick, etc.) ou hubs, utilizados para conectar outros dispositivos. A conexão de dispositivos pode ser feita em cascata ou em estrela. Os dispositivos USB não consomem recursos do sistema. Ao contrário dos dispositivos implementados seguindo padrões mais antigos, dispositivos USB não são mapeados em memória ou em endereços de I/O, nem utilizam IRQs e DMAs. Os únicos recursos de sistema utilizados por um sistema USB são as posições de memória utilizadas pelo software de sistema e as posições de memória e/ou endereços de I/O e IRQs utilizados pelo USB Host Controller. Os dispositivos contêm um número de registradores individuais, conhecidos como endpoints, que podem ser acessados indiretamente pelos device drivers. Quando uma transação é enviada pelo barramento, todos os dispositivos (exceto os de baixa velocidade) identificarão sua presença. Cada transação inicia com um pacote que determina o tipo de transação que será executada e o endereço do endpoint. Esse endereçamento é controlado pelo software USB. A Figura 1, extraída de [4], ilustra os elementos de hardware e software envolvidos em um sistema USB. Todas as transações são iniciadas pelo USB Client software. Esses acessos são tipicamente originados do USB device driver quando este necessita comunicar com seu respectivo dispositivo. O USB driver provê a interface entre o USB device driver e o USB host controller. É o responsável pela conversão da requisição do cliente em uma ou mais transações, que serão direcionadas do ou para o dispositivo alvo. Do ponto de vista do dispositivo, as mesmas funcionalidades estão refletidas em três estruturas: a primeira (Function) representa a interface funcional do dispositivo, que consiste de uma classe particular de dispositivos que podem ser manipulados por um mesmo driver; a segunda (Logical Device) pode ser vista como uma coleção de endpoints, sendo responsável por lidar com os mecanismos de transferência USB e as suas características; a terceira (Bus Interface), por sua vez, é responsável por receber e enviar sinais elétricos pelo cabo USB. HOST SYSTEM USB DEVICE Client Software (Client Driver) Function System Software Logical Device (USB Drv + HC Drv) Host Controller/Hub USB Cable Bus Interface FUNCTION LAYER USB DEVICE LAYER USB BUS INTERFACE LAYER Figura 1 - Fluxo de Comunicação em um sistema USB Os dispositivos USB (USB Devices) contêm um conjunto de descritores que especificam os atributos e características do dispositivo. Essa informação é necessária para que o host configure o dispositivo e localize seu respectivo driver, além de ser utilizada pelo device driver para acessar o dispositivo. Cada dispositivo possui um endpoint 0, que é reservado para configuração. É através desse endpoint que o software de sistema acessa os descritores do dispositivo. Os dispositivos USB podem ser implementados como de baixa ou alta velocidade. Os dispositivos de alta velocidade ‘enxergam’ todas as transações no barramento, enviando e recebendo dados à taxa de 12Mb/s. Os dispositivos de baixa velocidade estão limitados à taxa de 1,5Mb/s e só ‘enxergam’ as transações que seguem um pacote especial denominado preamble packet. As portas de baixa velocidade dos hubs ficam desabilitadas durante transações de alta velocidade, fazendo com que os dados que devem ser transmitidos à alta velocidade não transitem em cabos de baixa velocidade. O cliente USB requisita uma transferência e fornece um buffer de memória que será utilizado na mesma. O USB Host Controller Driver recebe a requisição e organiza a transferência em transações. O Host Controller gera a transação baseada no Descritor de Transferência, que foi construído pelo Host Controller Driver. Cada transação resulta na transferência de dados do buffer para o dispositivo, ou viceversa. Quando a transação é completada, o software do sistema notifica o driver cliente. Um dispositivo deve se descrever ao host software através de descritores (Descriptors), que se relacionam utilizando uma estrutura do tipo ´Árvore’, mostrada na Figura 2. Os descritores contêm informações sobre o dispositivo, suas configurações, classes, utilização de energia e características dos endpoints. Suas funcionalidades são descritas a seguir: § Device Descriptor: contém informações sobre o dispositivo, suas configurações e classes, além de fornecer as características do barramento de comunicação padrão que será utilizado para configurar o dispositivo. § § § Configuration Descriptors: informa características e habilidades de cada uma das configurações possíveis para um dispositivo, como por exemplo, utilização de energia e número de interfaces suportadas Interface Descriptors: contém informações relacionadas a uma interface, como por exemplo, classe e subclasse, e aos endpoints utilizados por ela. Endpoint Descriptors: contêm informações relativas ao endereço do endpoint, informando o número e direção deste, tamanho máximo do pacote de dados e frequência na qual este deve ser consultado para transferência de dados. Figura 2 – Descritores de Dispositivos Outros descritores podem ser utilizados conforme as características do periférico a ser implementado, tais como String Descriptors e Class Descriptors. String descriptors contêm uma descrição textual de determinadas características do periférico, tais como nome do fabricante, nome do produto, número serial, etc. Class Descriptors identificam o tamanho e o tipo dos descritores adicionais utilizados para descrever um determinado dispositivo pertencente a uma dada classe. 2.2 Classes USB Na especificação USB, dispositivos que possuem funções similares são agrupados em classes, de forma que se possa compartilhar funcionalidades comuns, além de utilizarem device drivers comuns. Cada classe de dispositivos pode definir descritores próprios (class-specific descriptors) e a inserção desses descritores na definição da estrutura geral do dispositivo é definida pelas próprias classes. Um dispositivo pode pertencer a uma única classe ou ser composto de várias classes. Por exemplo, um telefone possui elementos de áudio, interação humana (HID) e telefonia. Isso é possível em função da estrutura que descreve um dispositivo USB e a indicação desta característica é feita através da definição de várias interfaces. Para a implementação do conversor, foram estudadas duas classes em especial: a Communication Interface Device Class e a Human Interface Device Class. O conversor se enquadrou na classe HID e, por este motivo, esta é apresentada em maior grau de detalhes logo a seguir. 2.2.1 Human Interface Device Class (HID) De forma geral, a classe HID [3] consiste de dispositivos que são utilizados por pessoas para controlar a operação de sistemas de computação. Fazem parte desta classe dispositivos como mouse, teclados, controles utilizados em jogos e simulações, entre outros dispositivos. Inclui também dispositivos que, apesar de não requererem interação humana, provêm dados em um formato similar, como leitores de códigos de barras, termômetros, etc [4]. A classe HID define uma estrutura que descreve um dispositivo HID. Além dos descritores padrões definidos pela especificação de USB, esta utiliza um descritor de classe (Class Descriptor) através do qual podem ser definidos dois outros tipos de descritores: Report Descriptor e Physical Descriptor. Report Descriptors, diferentemente dos outros descritores, não consistem apenas de tabelas de valores. O tamanho e o conteúdo de um Report Descriptor varia dependendo do número de campos de dados necessários para descrever um dispositivo. É composto por itens que provêm informações sobre o dispositivo. Physical Descriptors descrevem parte ou partes do corpo utilizadas para se ativar um determinado controle. Neste trabalho não foram utilizados Physical Descriptors. A identificação do dispositivo como um HID é feita dentro do descritor de interface, sendo então definido um conjunto de descritores para cada Interface. A estrutura geral dos descritores para um dispositivo HID pode ser vista na Figura 2. Um dispositivo HID utiliza, normalmente, dois canais de comunicação, com transferências do tipo Interrupt e de Controle (endpoint 0), que são explicadas com maior detalhe na próxima seção. 2.3 Tipos de Transferências O barramento USB é um barramento compartilhado e que pode estar sendo utilizado, simultaneamente, por vários dispositivos. O driver cliente comunica ao driver USB que deseja efetuar uma transferência do/para o seu dispositivo correspondente. Algumas dessas transferências consistem de blocos maiores de dados, as quais precisam ser quebradas em várias transações. A transferência de dados é feita em intervalos regulares denominados frames. Um frame é composto de uma ou mais transações que devem ser executadas dentro de 1ms. Cada dispositivo USB é composto por uma coleção de registradores (endpoints) que podem ser acessados pelo driver cliente quando este necessita transferir dados ao seu dispositivo correspondente. Cada endpoint suport um determinado tipo de transferência. Estes são descritos a seguir: § Isochronous: taxa de transmissão de dados constante O foco desse tipo de transferência é garantir a entrega dos dados dentro de um determinado tempo, sendo dispensável a verificação de erros. Não pode ocorrer distorção no envio dos dados e o sincronismo é o foco deste tipo de transferência. Portanto, só é suportada por dispositivos de alta velocidade (12Mb/s). É unidirecional e possui um payload de dados de 1023 bytes/frame. Exemplos de dispositivos que utilizam este tipo de transferência são microfones, som de uma maneira geral. § Interrupt O objetivo desse tipo de transferência é verificar se algum dispositivo necessita de transferir algum dado para o host. Esse processo ocorre de tempos em tempos, e o intervalo é denominado Polling Interval. Dessa forma, o host ‘sonda’ os devices e caso seja necessário, a transferência é feita. É feita verificação de erros. Possui um payload de dados de 64 bytes/frame. Exemplo de dispositivos que utilizam este tipo de transferência são o teclado e o mouse. § Bulk Esse tipo de transferência é utilizado para blocos maiores de dados, onde a taxa de transferência não é fator relevante. O que é importante nesse tipo de transferência é o grau de correção em que os dados chegarão ao destino, sendo indispensável a verificação de erros. Possui um payload de dados de 8, 16, 32 ou 64 bytes por frame. A largura de banda disponível para esse tipo de transferência varia de acordo com a disponibilidade. Um bom exemplo de um dispositivo que utiliza esse tipo de transferência são as impressoras. § Controle Tipo de transferência utilizado pelo host para fazer requisições ao dispositivo. É nesse tipo de transferência que é feita a leitura dos descritores, por exemplo. Para esse tipo de transferência acontece também a verificação de erros. Transferências do tipo Isochronous e Interrupt têm uma maior prioridade em relação às outras quando distribuídas em um frame. Transferências do tipo Bulk só serão executadas quando houver disponibilidade de espaço dentro de um frame. 2.4 Requisições O protocolo USB é baseado em requisições, que são enviadas pelo host e processadas pelos dispositivos. Essas requisições possuem um limite de tempo para serem processadas pelos dispositivos que as recebem e são respondidas através do Default Control Pipe de cada dispositivo (endpoint 0). Essas requisições são feitas utilizando transferências de controle e a requisição e seus parâmetros são enviados ao dispositivo através de um pacote de setup. Existem algumas requisições que são comuns a todo tipo de dispositivo, enquanto outras são específicas de cada classe. As requisições devem ser direcionadas ao dispositivo, a uma interface dentro de um dispositivo ou ainda a um endpoint específico dentro de um dispositivo. Exemplos de requisições são Get_Configuration e Get_Descriptor, que retornam o valor da configuração e o descritor especificado respectivamente. O detalhamento das requisições será omitido pois foge do escopo deste documento. 3 Especificação do sistema Para o desenvolvimento de um dispositivo USB são necessários: § § § § § § 3.1 Um host que suporte USB Driver que execute no host e seja capaz de se comunicar com o periférico Aplicação que execute no host e permita o acesso ao periférico. Um microcontrolador com interface USB Implementação no microcontrolador do código responsável pela comunicação USB Implementação das demais funcionalidades do periférico no microcontrolador Requerimentos do Host A escolha do sistema operacional a ser utilizado pelo host, feita em 1999, baseou-se no suporte à tecnologia USB. O sistema operacional deveria prover toda uma infra-estrutura de drivers e suporte a características do protocolo, como por exemplo Plug and Play. Na época, os únicos sistemas operacionais que ofereciam tal suporte eram Windows95 OSR2 e Windows98, sendo que o primeiro estava ainda restrito a algumas aplicações. Por este motivo, a escolha do Windows98 como sistema operacional tornou-se evidente, uma vez que este trazia um melhor suporte à USB. O host deve ser capaz de receber dados USB utilizando para isso device drivers e disponibilizá-los às aplicações quando solicitados. É indispensável que tenhamos executando no host um driver capaz de efetuar as transferências USB (reconhecer o dispositivo, receber e enviar dados, etc). É desejável que tenhamos um driver virtualizado que simule o funcionamento de uma porta serial. Este driver deve ter a capacidade de obter os dados do driver USB e permitir com que uma aplicação os receba como se estivesse acessando uma porta serial. A presença deste driver não é indispensável, uma vez que as aplicações podem receber dados USB diretamente. É desejável uma vez que não seriam necessárias mudanças nas aplicações. Do ponto de vista das aplicações, estas devem ser capazes de receber e enviar dados, o que pode ser feito através de uma porta serial padrão (virtualizada) ou ainda modificando as aplicações para que estas acessem uma porta USB diretamente. A Microsoft provê um driver denominado USB POS driver [12], que foi implementado com o objetivo de permitir que as aplicações “enxergassem” dispositivos USB como se estes estivessem conectados a uma porta serial padrão. A estrutura geral do projeto é mostrada na Figura 5. Figura 5 – Estrutura geral do sistema 3.2 Requerimentos do dispositivo Foram definidos alguns requisitos a serem observados no processo de comunicação do dispositivo para a escolha do microcontrolador, como a velocidade de transmissão, a frequência em que estas ocorrem e o volume de dados a serem enviados e recebidos. Levando em consideração a velocidade de comunicação de dispositivos USB, verificou-se que um conversor de interface serial pode ser implementado como um dispositivo de baixa velocidade, com velocidade de transmissão variando de 10 a 100Kb/s. Em relação ao volume de dados transmitidos e frequência das transmissões, definiu-se a utilização de transferências do tipo Interrupt, na qual quantidades moderadas de dados devem ser transmitidas em períodos de tempo específicos. O host é responsável por verificar se o dispositivo possui dados a serem transmitidos em intervalos determinados de tempo. As transferências do tipo Interrupt podem ocorrer, não simultaneamente, nos dois sentidos, tanto para o envio de dados do PC ao conversor, quanto o contrário. Este tipo de interrupção é suportado pelo sistema operacional, que já disponibiliza drivers para a classe HID, a qual utiliza este tipo de transferência. O tamanho do pacote de dados para uma única transação, no caso de dispositivos de baixa velocidade, é de 8 bytes. Para envio de uma quantidade maior de dados, estes são subdivididos em múltiplas transações. Outra característica definida foi o número de endpoints que seriam necessários. Conforme explicado anteriormente, endpoints são estruturas capazes de armazenar múltiplos bytes sendo, tipicamente, blocos de dados na memória ou registradores de um microcontrolador. Cada endpoint possui um endereço único e uma direção pré-definida. Um caso especial trata-se do Endpoint 0, utilizado para controle, que permite transferência de dados bidirecional e é utilizado para configuração do dispositivo e troca de mensagens com o host. Foram definidos três endpoints, sendo um para o envio de dados ao host (IN) e outro para recepção (OUT), além do Endpoint 0 (controle), que está presente em todos os periféricos. A definição do número de endpoints foi feita em função da definição de POS driver da Microsoft, que traz como exigência a disponibilidade de um número par de endpoints além do Endpoint 0. Desta forma, o conversor de interface serial para USB pode ser implementado como um dispositivo da classe HID, que possui exatamente as características mencionadas acima. 4 Descrição do Hardware Com estes requisitos levantados, a escolha do microcontrolador a ser utilizado na implementação pôde ser feita, buscando aquele que tivesse, além das características especificadas, uma melhor documentação e outras aplicações já implementadas. A família CY7C634xx/5xx da Cypress é constituída de microcontroladores de 8 bits RISC, que utilizam arquitetura Harvard, possuem um número razoável de pinos de I/O, 256 bytes de RAM e de 4K a 8Kbytes de EPROM, variando de acordo com o microcontrolador. Esta família de microcontroladores é compatível com a versão 1.0 da Especificação USB [1]. Os microcontroladores desta família suportam três tipos de Reset: Power On Reset, WatchDog Reset e USB Bus Reset (non-hardware reset). Não possuem uma interface serial em hardware, a qual foi implementada utilizando-se pinos de I/O e um software que incorporasse as funções seriais. Todas as interrupções são mascaráveis através de dois registradores – Global Interrupt Enable Register e USB Endpoint Interrupt Enable Register. Cada interrupção está associada a um fragmento de código responsável por tratá-la. O conjunto de instruções foi otimizado para implementação de dispositivos USB, e todos os microcontroladores da família possuem um USB transceiver e uma USB Serial Interface Engine (SIE) (ver Figura 3 retirada de [6]). Apesar disto, estes microcontroladores podem ser utilizados para outras aplicação não-USB. A SIE permite que o microcontrolador se comunique com o host. Ela simplifica a interface entre o microcontrolador e o host, incorporando o hardware que lida com as atividade do barramento independentemente do controlador. Os microcontroladores da família CY7C634xx/5xx provêm um endereço para o dispositivo e três endpoints. O endereço é atribuído ao dispositivo e salvo em um registrador - USB Device Address Register (7 bits) - durante o processo de enumeração. O USB Controller comunica-se com o Host utilizando buffers dedicados, um por endpoint. Cada buffer é implementado como um conjunto de 8 bytes de memória SRAM e seu status e controle é feito utilizando os registradores Mode Register e Count Register. A organização da memória RAM pode ser vista na Figura 4, onde estão salientados os endereços de memória utilizados pelos endpoints. Figura 4 – Organização da memória RAM dos microcontroladores da família CY7C634xx/5xx da Cypress Figura 3 – Diagrama de blocos lógicos Foi utilizado o kit de desenvolvimento (CY3651) correspondente à família CY7C634xx/5xx, o que permitiu uma maior flexibilidade em relação à quantidade de memória necessária para o código. 5 Projeto e implementação do Software O desenvolvimento do conversor pode ser dividido em algumas fases: § § § § § § Definição dos descritores Implementação das rotinas para tratamento de interrupções Módulo de detecção e enumeração do dispositivo Módulo de troca de dados USB Módulo de troca de dados seriais Interseção dos módulos USB e Serial para que trabalhassem conjuntamente A definição de fases não implica que estas devam ser executadas de forma independente (disjunta). 5.1 Definição dos descritores A principal estrutura de dados a ser implementada é formada por um conjunto de descritores de dispositivo que, definidos na especificação USB [1], armazenam características do periférico e permitem que o host o conheça melhor. Cada um dos descritores contém informações sobre o dispositivo como um todo ou de suas partes. O Device Descriptor contém informações básicas sobre o dispositivo e é o primeiro a ser lido pelo host. É através dele que o host consegue acessar os demais descritores de forma a obter toda a informação necessária para a configuração do dispositivo. Os valores de seus campos foram definidos de acordo com as características do conversor [8]. Para a implementação de um novo dispositivo, estes valores devem ser reavaliados e modificados, caso necessário. A versão da especificação de dispositivos HID utilizada para a implementação foi a v1.1 [3]. As especificações da classe e subclasse do dispositivo são feitas no descritor de interface, pois o dispositivo foi implementado como um HID. O descritor do dispositivo foi definido utilizando o código de vendedor da Lakeview Research (0925h), por ter sido baseado em um exemplo fornecido juntamente com um livro desta editora [5]. Portanto, foi definido para o código do dispositivo o valor 0x1234h, por se tratar de uma aplicação exemplo. Não foi necessário obter um código próprio para a Engetron por este projeto se tratar apenas de um protótipo. Para a implementação efetiva seria necessário obter um Vendor ID através da associação ao USB Implementers Forum. Para um conversor genérico, os campos em destaque devem ser alterados de acordo com as requisições do dispositivo e da versão da especificação que estiver sendo utilizada. Device Descriptor (18 bytes) Campo Blength BDescriptorType BcdUSB BDeviceClass BDeviceSubClass BDeviceProtocol BMaxPacketSize0 IdVendor Offset / tamanho (bytes) 0/1 1/1 2/2 4/1 5/1 6/1 7/1 8/2 IdProduct BcdDevice IManufacturer 10/2 12/2 14/1 IProduct 15/1 ISerialNumber bNumConfigurations 16/1 17/1 Descrição Valor Assumido Tamanho deste descriptor (em bytes) Tipo do descriptor (device descriptor) Versão da especificação de USB utilizada Classe do dispositivo3 Subclasse do dispositivo3 Código do protocolo qualificado pela subclasse3 Tamanho máximo do pacote para o endpoint0 Identificação do vendedor 0x12 0x01 0x0110 0x00 0x00 0x00 0x08 0x0925 (Lakeview Research) Identificação do dispositivo 0x3412 (exemplo) Versão do dispositivo 0x01 Índice para o string descriptor contendo a identificação do 0x00 (nenhum)4 fabricante Índice para o string descriptor contendo a identificação do 0x00 (nenhum) 4 produto Índice para o string descriptor contendo o número de série 0x00 (nenhum) 4 Número de configurações possíveis 0x01 Tabela 1 – Definição do Device Descriptor Uma configuração descreve as características e capacidades do periférico e, de forma geral, uma única configuração é suficiente. Dispositivos com vários modos ou casos de uso podem ter várias configurações e, consequentemente, vários descritores de configuração. Para o conversor implementado, foi definida uma única configuração, que utiliza a energia fornecida pelo barramento (Bus Powered) até um limite máximo de 100mA. 3 A definição da classe, subclasse e protocolo para um dispositivo foi feita dentro do descritor de interface pois o dispositivo implementado possui apenas uma configuração e uma interface. 4 Podem ser definidos descritores para descrever melhor atributos como nome do fabricante, do dispositivo e número serial. Para o caso, não foi necessário. Configuration Descriptor (9 bytes) Campo bLength bDescriptorType wTotalLength Offset / tamanho (bytes) 0/1 1/1 2/2 bNumInterfaces bConfigurationValue iConfiguration bmAttributes 4/1 5/1 6/1 7/1 MaxPower 8/1 Descrição Valor Assumido Tamanho deste descriptor (em bytes) Tipo do descriptor (configuration descriptor) Tamanho total de todos descriptors (configuration+interface+endpoint+HID) Número de interfaces (conversor) Valor utilizado para selecionar essa configuração Índice do string descriptor que descreve esta configuração Características da configuração (em relação à energia) Bus Powered Quantidade máxima de energia consumida do barramento pelo dispositivo (expressa em 2mA) 0x09 0x02 0x22 (34 bytes) 0x01 0x01 0x00 (nenhum)4 0x80 0x32 (100 mA) Tabela 2 – Definição do Configuration Descriptor Uma interface define, para um dispositivo, um conjunto de endpoints utilizados para uma característica ou funcionalidade. Um descritor de interface contém, assim, informações relacionadas aos endpoints. A utilização de múltiplas interface acontece quando um dispositivo possui várias formas de utilização, implicando em endpoints com características diferentes. Para o conversor, foi definida uma única interface com três endpoints, sendo um de controle, um Interrupt IN e outro Interrupt OUT. A utilização de um endpoint Interrupt OUT não foi possível, pois o suporte provido pelo Windows98 estava ainda restrito. Sendo assim, foram utilizados dois endpoints, um de controle e o outro Interrupt IN. Os dados são enviados ao dispositivo através do Endpoint 0 e ao PC através do Endpoint 1, utilizando as rotinas SET REPORT e GET REPORT respectivamente. Interface Descriptor (9 bytes) Campo bLength bDescriptorType bInterfaceNumber Offset / tamanho (bytes) 0/1 1/1 2/1 bAlternateSetting 3/1 bNumEndpoints 4/1 bInterfaceClass bInterfaceSubClass bInterfaceProtocol Interface 5/1 6/1 7/1 8/1 Descrição Valor Assumido Tamanho deste descriptor (em bytes) Tipo do descriptor (interface descriptor) Número da interface que identifica o índice no array de interfaces concorrentes para esta configuração Valor utilizado para selecionar configurações alternativas para esta interface Número de endpoints utilizados por esta interface (excluindo o endpoint0) - 1 Interrupt IN Código de classe para esta interface Código de subclasse Código do protocolo (0 = none) Índice do string descriptor que descreve esta interface 0x09 0x04 0x00 0x00 0x01 0x03 (HID) 0x00 (nenhum)5 0x00 (nenhum)5 0x00 (nenhum)5 Tabela 3 – Definição do Interface Descriptor Exceto para o Endpoint 0, cada endpoint especificado em uma interface está associado a um descritor de endpoint. O Endpoint 1 foi definido como sendo do tipo Interrupt, cuja direção de tráfego de dados é do dispositivo para o host, ou seja, IN. O tamanho dos pacotes de dados foi definido para o valor máximo em transferências do tipo Interrupt - 8 bytes – e o menor intervalo de pooling para dispositivos de baixa velocidade - 10ms., de forma a permitir a transferência de um volume maior de dados em cada transação. 5 Podem ser definidos descritores para descrever melhor atributos como nome do fabricante, do dispositivo e número serial. Para o caso, não foi necessário. Endpoint Descriptor (7 bytes) - Endpoint1 - Interrupt IN Endpoint Campo bLength bDescriptorType bEndpointAddress Offset / tamanho (bytes) 0/1 1/1 2/1 bmAttributes 3/1 wMaxPacketSize 4/1 bInterval 6/1 Descrição Valor Assumido Tamanho deste descriptor (em bytes) Tipo do descriptor (endpoint descriptor) Endereço do endpoint no dispositivo USB bit 0..3 número do endpoint bit 4..6 reservado bit 7 direção (0=OUT 1=IN) Atributos do endpoint quando configurado utilizando o bConfigurationValue bit 0..1 Tipo de transferência Tamanho máximo de pacote que o endpoint é capaz de enviar e receber (de 8 a 64bytes) Intervalo para verificação se o endpoint possui dado a ser transferido (em ms) 0x07 0x05 0x81 0x03 0x0008 0x0A (10 ms) Tabela 4 – Definição do Endpoint Descriptor String Descriptors não foram definidos por serem dispensáveis no caso do protótipo. O objetivo de descritores de classe é identificar informações adicionais a serem utilizadas na comunicação, podendo assim fazer um melhor uso das características do dispositivo. Podem possuir sete ou mais campos, dependendo do número de descritores adicionais utilizados. Para o conversor, foi utilizado apenas um descritor adicional, o Report Descriptor. HID (Class) Descriptor (9 bytes) Campo bLength bDescriptorType bcdHID bCountryCode Offset / tamanho (bytes) 0/1 1/1 2/2 4/1 bNumDescriptors bDescriptorType wDescriptorLength 5/1 6/1 7/2 Descrição Valor Assumido Tamanho deste descriptor (em bytes) Tipo do descriptor (hid (class) descriptor) Versão da especificação da classe HID (em BCD) País de origem do hardware 00 = não suportado Número de descriptors para a classe HID Tipo do descritor adicional - Report descriptor Tamanho total do report descriptor 0x09 0x21 0x0110 0x006 0x01 0x22 end_hid_report_desc_table – hid_report_desc_table Tabela 5 – Definição do descritor de classe – HID Descriptor A definição de país de origem só é utilizada quando o hardware é desenvolvido para um país específico, o que não foi o caso. Report Descriptors definem o formato e utilização dos dados que implementam as funcionalidades do dispositivo. Para o conversor, foram definidas apenas duas funcionalidades, uma de envio e outra de recebimento de dados. A recepção de dados pelo conversor é feita através de Output Reports, estrutura que foi definida como sendo formada por 16 campos de 8 bits em função do tamanho dos comandos enviados ao no-break. O envio de dados do conversor ao host é feito diretamente, uma vez que estes são recebidos da interface serial e encaminhados à interface USB (Endpoint buffer), sendo lidos através de uma requisição. Neste caso, não foi definido um Input Report que implemente esta funcionalidade, pois não é feito o armazenamento da resposta recebida. 6 Caso se deseje implementar uma versão de dispositivo específica para um país, utilizar este campo para indicar o código do país. O código associado à implementação do Report Descriptor é mostrado abaixo. O código implementado para os demais descritores pode ser encontrado no Anexo A. ;The HID-report descriptor table hid_report_desc_table: db 06h, A0h, FFh db 09h, 01h db A1h, 01h db 09h, 02h db A1h, 00h db 06h, A1h, FFh ; ; ; ; ; ; ;The output report db 09h, 05h db 09h, 06h db 15h, 80h db 25h, 7Fh db 35h, 00h db 45h, FFh db 75h, 08h db 95h, 0Bh db 91h, 02h ; ; ; ; ; ; ; ; ; db C0h ; db C0h ; Usage Page (vendor defined) FFA0 Usage (vendor defined) Collection (Application) Usage (vendor defined) Collection (Physical) Usage Page (vendor defined) usage - vendor defined usage - vendor defined Logical Minimum (-128) Logical Maximum (127) Physical Minimum (0) Physical Maximum (255) Report Size (8) (bits) Report Count (11) (fields) Output (Data, Variable, Absolute) End Collection End Collection end_hid_report_desc_table: 5.2 Rotinas de interrupção nos Endpoints A implementação das rotinas que tratam da comunicação USB foi feita baseada em códigos de exemplo de teclado, entre outras aplicações USB, fornecidas pela Cypress [8,9] ou extraídas do livro USB Complete [10], muitas destas implementadas para a família CY7C6300x. As rotinas para tratamento de interrupção (ISR) são chamadas uma vez que o host ou o controlador enviem um pacote ao barramento. Estas checam o tipo de pacote enviado ou recebido pelo seu respectivo endpoint e provêm o tratamento da requisição. Existem ainda rotinas de interrupção para cada um dos endpoints. Estas acontecem após o host ou o controlador enviarem um pacote ao barramento. São responsáveis por checar qual o tipo do pacote que está sendo enviado/recebido pelo respectivo Endpoint. A rotina referente à interrupções no Endpoint 0 é mostrada a seguir. Ela recebe uma conjunto de dados do host e verifica sua validade (correção de erros, completeza e ordem). Basicamente, esta rotina espera até que um pacote de SETUP seja recebido, processando então a requisição e tomando a ação apropriada: enumeração, configuração ou troca de dados. Uma vez que uma requisição é recebida, ela desabilita as interrupções no Endpoint 0 a fim de poder processar o pacote recebido. As demais interrupções devem ser habilitadas, neste caso, de forma a permitir que o microcontrolador responda a elas. A parte do código referente ao recebimento e tratamento da requisição está destacado. USB_EP0_ISR: save accumulator on stack if we did not receive a SETUP token packet – done_EP0 if data is not valid - STALL_IN_OUT if we did not receive 10 data bytes (8 data bytes + 2 CRC bytes) – STALL_IN_OUT if data toggle is not 0 – STALL_IN_OUT disable just endpoint zero interrupt (other interrupts enabled) parse SETUP packet test bmRequestType ; Find out whether the request is a standard device or HID request, the direction of data transfer, and ; whether the request is to a device, interface, or endpoint. (from Table 9.2 in the USB spec) test bRequest ; Find out which request it is. process the request ; Process the request. handshake with the host STALL_IN_OUT: accept SETUP, stall IN/OUT enable endpoint zero interrupt done_EP0: restore accumulator from stack and return Conforme dito anteriormente, o Endpoint 1 é do tipo Interrupt IN, ou seja, é utilizado para envio de dados ao host. A rotina de interrupção é chamada toda vez que o host deseja obter dados do Endpoint 1. Os dados a serem enviados já devem estar disponíveis para leitura pela requisição de GET REPORT, ou seja, a rotina apenas prepara para o envio do próximo pacote. A interrupção acontece quando o host responde ao dispositivo afirmando o recebimento de um pacote de dados no Endpoint 1 (ACK). O bit que indica o tipo de pacote de dados é então modificado (Data Packet type - de 0 para 1 ou de 1 para 0), conforme a especificação. A rotina de interrupção do Endpoint 1 é mostrada a seguir. USB_EP1_ISR: save accumulator on the stack test whether ACK bit is set to know that the last data transmission was successful if we don’t have an ACK bit – doneEP1 toggle the data 0/1 bit so it’s correct for the next transaction. clear endpoint 1 FIFO doneEP1: restore accumulator from the stack return from interrupt 5.3 Detecção e enumeração do dispositivo A terceira fase do projeto consiste na implementação do código que permite que o host detecte e enumere o dispositivo. A implementação destas rotinas foi baseada em códigos de exemplo [8,9,10]. Devemos ter implementado dentro do microcontrolador o código que permita o acesso aos descritores, o reconhecimento e tratamento das requisições que o host envia para enumeração do dispositivo. Do ponto de vista do host, deve ser criado um arquivo de extensão .INF [5], de forma que o Windows possa identificar o dispositivo e enumerá-lo, associando a ele o driver adequado. O sistema operacional provê arquivos .INF de exemplo, e por este motivo, é apresentado no Anexo A apenas as rotinas implementadas no microcontrolador. 5.4 O processo de envio e recepção de dados O envio de dados ao no-break ocorre através de transferências de controle utilizando a requisição SET REPORT e o Endpoint 0. Estas transferências possuem uma fase de SETUP, uma ou mais fases de DADOS e uma fase de STATUS. O host deve, primeiramente, enviar uma requisição ao dispositivo, indicando que ele deseja enviar dados. Uma vez identificado o tipo da requisição, é chamada uma rotina que recebe os dados, armazenado-os no Endpoint 0. Tendo preenchido o buffer do Endpoint 0, os dados são copiados em outro buffer para que sejam enviados ao no-break através da interface serial. É chamada então a rotina que envia os dados (comando) byte a byte ao no-break, inserindo Start e Stop bits e atrasos necessários. Podem ser lidos 8 ou 16 bytes, valor este que é informado no cabeçalho da requisição, na fase de SETUP. O tamanho máximo do pacote de dados a ser enviado foi definido em função do maior comando a ser recebido pelo no-break. Para a implementação de um conversor mais genérico, esta função deve ser alterada de forma a permitir o recebimento de um número arbitrário de bytes. Uma vez enviado o comando, o controlador se prepara para receber a resposta do no-break, armazenando-a da mesma forma em um buffer de recepção. O buffer de recepção é gradativamente preenchido pelas rotinas da serial e lido pela rotina de GET REPORT. Caso todo o processo de envio de comandos e recepção da resposta ocorra com sucesso, é enviado ao host um pacote de ACK (acknowledge) através da rotina NO_DATA_CONTROL. Caso contrário, desconsidera-se o que foi enviado e/ou recebido. A leitura de dados do no-break ocorre através de transferências do tipo interrupt utilizando a requisição GET REPORT e o Endpoint 1. O host envia uma requisição ao dispositivo, indicando que ele deseja receber dados. Os dados são obtidos da serial e copiados para o buffer do Endpoint 1, para que estes sejam enviados ao host. As principais rotinas implementadas encontram-se no Anexo A. 6 Drivers e aplicações A comunicação com dispositivos USB pode ser implementada em qualquer linguagem que possibilite a chamada de funções da API do Windows [11]. Os drivers necessários para comunicação com dispositivos HID já são fornecidos juntamente com o sistema operacional. Para dispositivos que se enquadrem em outras classes ou em nenhuma delas pode ser necessário que se escreva o device driver correspondente. A comunicação com um dispositivo HID envolve operações mais complexas do que as existentes para portas baseadas em padrões mais antigos. Antes mesmo de executar as funções de “abrir” uma porta, setar parâmetros, ler e escrever dados, é necessário encontrar o dispositivo e obter informações a respeito do mesmo e seus Reports. Esse processo é feito através de uma série de chamadas à funções de API. Primeiramente, a aplicação procura por todos os dispositivos HID presentes no sistema, examinando então as informações de cada um até encontrar aquele com os atributos desejados. Após encontrar o dispositivo desejado, a aplicação pode trocar informações com este através dos Reports. 7 Conclusões O conversor foi implementado e testado utilizando o kit de desenvolvimento da Cypress e ferramentas fornecidas pelo USB Implementers Forum. O código implementado responde corretamente a todos os tipos de requisições (enumeração e troca de dados). Todo o processo de troca de dados, incluindo a conversão de protocolos, foi validada utilizando uma aplicação que emulasse a serial do dispositivo (similar ao no-break) e uma outra aplicação, rodando no host, e que se comunica via USB. Este documento é um bom guia àqueles que desejam desenvolver dispositivos USB, uma vez que ele apresenta todas as etapas do desenvolvimento de forma clara e baseada no exemplo implementado. Os principais algoritmos implementados são mostrados no Anexo A. Como trabalho futuro, deseja-se utilizar drivers para a virtualização da porta serial. Isto permitirá que as aplicações possam acessar dados USB sem a necessidade de serem modificadas. Agradecimentos Este trabalho foi desenvolvido com o apoio financeiro provido por uma parceria entre a Engetron e o Departamento de Ciência da Computação. Referências Bibliográficas [1] USB Implementers Forum, “Universal Serial Bus Specification,” Revision 1.0, 1996. [2] USB Implementers Forum, “Universal Serial Bus Specification,” Revision 1.1, 1998. [3] USB Implementers Forum, “Device Class Definition for Human Interface Devices,” Revision 1.1, 1999 [4] D. Anderson, “Universal Serial Bus System Architecture ”. Addison-Wesley Developers Press, 1997. [5] J. Axelson, “USB Complete – Everything You Need to Develop Custom USB Peripherals”. Lakeview Research, 1999. [6] Cypress, “CY7C63411/12/13 and CY7C63511/12/13 Low-Speed, High I/O, 1.5 Mbps USB Controller”, 1998, available at http://www.cypress.com/ usb/datasheets.html (microcontrollers datasheet) [7] Cypress, “USB Development System CY3651 User’s Guide”, Revision 2.2, 1997. [8] Cypress, “Designing a Low-Cost USB Interface for an Uninterruptable Power Supply with the Cypress Semiconductor CY7C630001 USB Controller”, 1998, available at http://www.cypress.com/ usb/usbappnotes.html [9] Cypress, “CY3651 test firmware”, 1999 (Application notes) [10] J. Axelson, “Usbhidio - Universal Serial Bus Human Interface Device Input/Ouput (I/O) application” [11] Microsoft, Windows 98 DDK [12] Microsoft USB Point Of Sale, available at http://www.microsoft.com/HWDEV/usb/posusb.htm ANEXO A ;====================================================================== ;interrupt vectors ;====================================================================== ORG 00h jmp jmp jmp Reset USB_Bus_Reset_ISR Serial_ISR jmp jmp jmp jmp jmp jmp jmp jmp jmp jmp One_mSec_ISR USB_EP0_ISR USB_EP1_ISR DoNothing_ISR Reset DoNothing_ISR DoNothing_ISR DoNothing_ISR GPIO_ISR DoNothing_ISR ORG ; ; ; ; ; ; ; ; ; ; ; ; ; ; Reset vector; begin here after a reset. USB bus reset Used to implement serial receive/transmit bit timing loops – declared on serial.asm 1024-millisecond interrupt endpoint 0 interrupt endpoint 1 interrupt endpoint 2 interrupt reserved interrupt reserved interrupt reserved interrupt DAC interrupt GPIO interrupt – declared on serial.asm reserved interrupt 1Ah include "serial.asm" ;====================================================================== ;Interrupt routines ;====================================================================== DoNothing_ISR: reti ; return from interrupt ; When the part detects a single-ended zero from the upstream port, ; an interrupt occurs that wakes up the CPU from suspend mode and ; transfers control to this interrupt service routine. ; clears watchdog timer - clears USB device address register ; The USB Bus Reset interrupt service routine prepares for enumeration. USB_Bus_Reset_ISR: push A ; save accumulator on stack iowr Watchdog ; clear watchdog timer iord Status_Control ; clear the Bus Reset bit in the and A, ~USBReset ; Status & Control register iowr Status_Control mov A, TIMER_MASK ; enable one msec timer interrupt iowr Global_Interrupt mov A, ENDPOINT_ZERO_ONLY ; enable endpoint zero interrupt iowr Endpoint_Interrupt ; disable endpoint one&two interrupt iord EP_A0_Mode ; unlock Mode register mov A, SETUP ; enable endpoint zero setup iowr EP_A0_Mode mov A, ADDRESS_ENABLE_BIT iowr USB_Device_Address mov A, DISABLED iowr EP_A1_Mode iowr EP_A2_Mode ; enable endpoint zero response iowr Watchdog call Init iowr Watchdog ; clear Watchdog timer ; perform initialization on variables ; clear Watchdog timer again ; disable endpoint one ; disable endpoint two pop A reti ; restore accumulator from stack ; return from interrupt ;---------------------------------------------------------------------; 1-millisecond interrupt - Check to see if the chip is in suspend ; mode and take appropriate action. Copy values to Endpoint 1's buffer for sending. ;---------------------------------------------------------------------One_mSec_ISR: push A iowr Watchdog ;clear watchdog timer ;Find out if enumeration is complete. ;If enumerating is in progress, loop_temp = 0. mov A, [loop_temp] cmp A, 0h ;If enumeration is still in progress, jump. jz not_main ;Enumeration has ended, so decrement the loop counter ;(so it no longer = 0). dec [loop_temp] mov cmp jnz inc jmp A, [wakeup_flag] A, 01h not_sending_wakeup [wakeup_counter] One_mSec_end not_sending_wakeup: dec [4ms_counter] jnz not_main mov A, 4 mov [4ms_counter], A mov A, [idle_period_counter] cmp A, FFh jz not_main inc [idle_period_counter] ;check if we are in a remote ;wakeup process ;increment wakeup counter ;indicate if the 10msecs of wakeup ;have passed ;decrement the 4msecs counter ;if not zero, then check bus activity status ;if zero, re-initialize to 4msecs ;since 4msecs has passed, increment ;the idle_period_counter ;which has a 4msecs resolution ;if the idle_period_counter reaches FFh, ;don't increment ;to prevent rollover not_main: ;Check for bus activity. iord USB_Status_Control ;check if there is no bus activity and A, 08h ;bit 3 = bus activity cmp A, 0h ;If no bus activity, increment the suspend counter. jz Inc_counter ;If bus activity detected, clear the bus-activity bit, iord USB_Status_Control and A, 0F7h iowr USB_Status_Control ;and clear the suspend counter. mov A, 0h mov [suspend_counter], A jmp Suspend_end Inc_counter: ;Keep track of the amount of time with no bus activity. inc [suspend_counter] ;Get the number of milliseconds the bus has been idle. mov A, [suspend_counter] ;Has it been 3 milliseconds? = check if 3msecs of bus inactivity passed cmp A, 03h ;If no, there's nothing else to do. = less than 3msecs jnz Suspend_end ;If yes, put the chip in Suspend mode. ;Clear the Suspend counter. mov A, 0h mov [suspend_counter], A mov A, [remote_wakeup_status] cmp a, ENABLE_REMOTE_WAKEUP jnz Suspend_controller ; is remote wakeup feature enabled? ; ; if not, just do a normal suspend mov A, GPIO_ONLY_MASK iowr Global_Interrupt ei ; enable GPIO interrupts ; enable interrupts Suspend_controller: mov A, RESISTIVE_NEG ; all ports resistive neg so that iowr GPIO_Config ; we stay within suspend current budget ;In "Resistive" mode, a 7 Kohm pull-up resistor is conditionally enabled ;for all pins of a GPIO port. The resistor is enabled for any pin that has been ;written as a "1". The resistor is disabled on any pin that has been written ;as a "0". iord Status_Control or A, 08h iowr Status_Control ;The chip is now in Suspend mode. ;On exiting Suspend mode, the chip will begin ;executing instructions here: nop ;Disable pullups on Port 1. Enable the output DAC. mov A, NORMAL ; restore original GPIO configuration iowr GPIO_Config Suspend_end: ;Is endpoint 1 enabled? iord EP_A1_Mode and A, 0Fh cmp A, 0 ;If no, do nothing. jz Select ;If yes, is start_send = 1? ;(Start_send adds a short delay after enumeration.) mov A, [start_send] cmp A, 01h ;If no, do nothing jnz Select ;If yes, send data: jmp send_value send_value: ;Copies values from RAM into Endpoint 1's buffer ;and enables sending the bytes on the next poll. ;disable Endpoint 1 interrupts iord Endpoint_Interrupt ; disable endpoint one interrupt and A, ~ENDPOINT_ONE iowr Endpoint_Interrupt ;Copy values from RAM to Endpoint 1's buffer for transmitting to the host. mov A, [Data_Byte0] mov [Endpoint1_Byte0], A mov A, [Data_Byte1] mov [Endpoint1_Byte1], A mov A, [Data_Byte2] mov [Endpoint1_Byte2], A mov A, [Data_Byte3] mov [Endpoint1_Byte3], A mov mov mov mov mov mov mov mov A, [Data_Byte4] [Endpoint1_Byte4], A, [Data_Byte5] [Endpoint1_Byte5], A, [Data_Byte6] [Endpoint1_Byte6], A, [Data_Byte7] [Endpoint1_Byte7], A A A A ;Configure Endpoint 1's transmit register ;so that the bytes will transmit on the next poll. iord [EP_A1_Counter] ;Don't change the Data 0/1 bit. and A, 80h ;Set bits 4 and 7 to 1 enable transmitting. or A, 08h ; sending 8 bytes iowr [EP_A1_Counter] Select: ;Enable Endpoint 1 interrupts. iord Endpoint_Interrupt or A, ENDPOINT_ONE iowr Endpoint_Interrupt One_mSec_end: pop A reti ; return from interrupt ;====================================================================== ; The main program loop. ;====================================================================== main: ;Find out if the loop_temp delay has timed out. ;Loop_temp =0 if not timed out, FFh if timed out. mov A, [loop_temp] cmp A, 0Ah ;If no, don't enable transmitting. jnc no_set ;If yes, enable transmitting. mov A, 01h mov [start_send], A no_set: ;Clear the watchdog timer. ;This has to be done at least once every 8 milliseconds! iowr Watchdog iord Port0_Data nochange: jmp main ;---------------------------------------------------------------------; This is the routine that is entered when a data item from the UPS is ; requested and returns with the string resident in the receive buffer ;---------------------------------------------------------------------putCommand: mov A, 00h ; Clear the accumulator. mov [txBufPtr], A ; Reset tx buffer pointer. iowr Watchdog ; Clear watchdog timer. mov X, [txBufPtr] ; Set the index. mov A, [X + txBuf] ; Load Command Byte Zero. mov [txData], A ; Put it in the transmit register. inc [txBufPtr] ; Point to the next byte. iowr Watchdog ; Ping the watch dog timer. call txRoutine ; Go transmit the byte. iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog ; Ping the watch dog timer. ; Give the serial device some time. ; Set the index. ; Load Command Byte One ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Two ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Three ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Set the index. ; Load Command Byte Four. ; Put it in the transmit register. ; Point to the next byte. ; Ping the watch dog timer. ; Go transmit the byte. ; Ping the watch dog timer. ; Give the serial device some time. ; Set the index. ; Load Command Byte Five ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Six ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Seven ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time ; Set the index. ; Load Command Byte Eight. ; Put it in the transmit register. ; Point to the next byte. ; Ping the watch dog timer. call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc iowr call iowr call mov mov mov inc call txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] Watchdog txRoutine Watchdog delay1 X, [txBufPtr] A, [X + txBuf] [txData], A [txBufPtr] txRoutine mov iowr iowr ret A, 80h Port0_Interrupt Watchdog ; Go transmit the byte. ; Ping the watch dog timer. ; Give the serial device some time. ; Set the index. ; Load Command Byte Nine ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Ten ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Eleven ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time ; Set the index. ; Load Command Byte Twelve ; Put it in the transmit register. ; Point to the next byte. ; Ping the watch dog timer. ; Go transmit the byte. ; Ping the watch dog timer. ; Give the serial device some time. ; Set the index. ; Load Command Byte Thirteen ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Fourteen ; Put it in the transmit register. ; Point to the next byte. ; clear watchdog timer ; Go transmit the byte. ; clear watchdog timer ; Give the serial device some time. ; Set the index. ; Load Command Byte Fifteen ; Put it in the transmit register. ; Point to the next byte. ; Go transmit the byte. ; ; ; ; Enable the Port 0 Bit 7 GPIO interrupt. clear watchdog timer Return to sender. ;======================================================================== ; function: no_data_control ; purpose: performs the no-data control operation ; as defined by the USB specifications ; (i.e. respond to Status IN token with 0 byte data) ; ; premature_setup is set to 1 (true) if a SETUP is received during ; the routine, otherwise it is set to 0 (false) no_data_control: push A mov A, STATUSINONLY ; enable TX0 IN call set_ep0_mode mov A, [premature_setup] ; return if we received a premature cmp A, 0 ; SETUP jnz done_nodata_control ; wait for the zero-length transfer to complete wait_nodata_sent: iord EP_A0_Mode ; read mode register and A, EP0_SETUP_RCV ; did we receive premature SETUP? jz check_nodata_ack mov A, 1 ; yes, then set flag and return mov [premature_setup], A jmp done_nodata_control check_nodata_ack: iord EP_A0_Mode and A, ACK_BIT jz wait_nodata_sent ; read mode register ; wait for ACK bit high ; clear ACK bit mov A, SETUP call set_ep0_mode ; NAK IN and OUT packets done_nodata_control: pop A ret ; return ;======================================================================== ; function: Control_write ; purpose: performs the control_write operation ; as defined by the USB specifications for 1 OUT byte: ; SETUP - OUT - IN ; This routine does not acknowledge the Status IN packet from the host. ; The no_data_control routine will do this. ; data_ok is set to 1 (true) if valid data is received, ; otherwise it is set to 0 (false) ; premature_setup is set to 1 (true) if a SETUP is received during ; the routine, otherwise it is set to 0 (false) Control_write: mov A, ACKOUTSTATUSIN call set_ep0_mode mov A, [premature_setup] cmp A, 0 jnz done_control_write ; wait until we get the OUT byte wait_control_write_OUT: iord EP_A0_Mode and A, EP0_OUT_RCV jnz control_write_check_OUT iord EP_A0_Mode and A, EP0_SETUP_RCV jz wait_control_write_OUT ; accept OUT, enable TX0 IN ; return if we received a premature ; SETUP ; read mode register ; did we get the OUT packet? ; read mode register ; did we receive premature SETUP? mov A, 1 mov [premature_setup], A jmp done_control_write ; yes, then set flag and return ; we received the OUT byte, make sure the data is valid control_write_check_OUT: iord EP_A0_Counter ; read counter register and A, DATAVALID ; is data valid? ; data is invalid, and the SIE did not send ACK the host will try to send data ; again, so jump back to the beginning jz Control_write iord EP_A0_Counter ; read counter register and A, COUNT_MASK cmp A, 03h ; is count 3 (1 OUT byte + 2 CRC)? jnz control_write_send_stall mov A, NAKINOUT call set_ep0_mode ; accept SETUP, NAK IN/OUT mov A, [premature_setup] cmp A, 0 jnz done_control_write ; return if we received a premature ; SETUP ; wait until host sends us Status IN control_write_wait_IN: iord EP_A0_Mode and A, EP0_OUT_RCV jnz Control_write ; ; ; ; read mode register did we get an OUT? if so, jump back to beginning to get new OUT byte iord EP_A0_Mode ; read mode register and A, EP0_IN_RCV ; did we get the Status IN? jnz control_write_status_in ; if so, return so that we can ; process the OUT byte before no_data_control responds to the Status IN iord EP_A0_Mode and A, EP0_SETUP_RCV jz control_write_wait_IN mov A, 1 mov [premature_setup], A jmp done_control_write ; ; ; ; read mode register did we get a premature SETUP? no, then wait for Status IN yes, set flag and return control_write_send_stall: ; set data_ok flag to false so that calling routine does not use the bad data mov A, 0 mov [data_ok], A call SendStall jmp done_control_write control_write_status_in: ; set data_ok flag to true so that calling routine knows data is good mov A, 1 mov [data_ok], A done_control_write: ret ; return ;====================================================================== ;Lookup Tables ;Contain the descriptors and the codes for status indicators. ;The firmware accesses the information by referencing a specific ;table's address as an offset from the control_read_table. ;====================================================================== control_read_table: device_desc_table: db 12h db 01h db 10h,01h db 00h db 00h db 00h db 08h db 25h,09h db 34h,12h db 01h,00h ; ; ; ; ; ; ; ; ; ; db 01h ; db 00h ; db 00h ; db 00h ; db 01h ; end_device_desc_table: config_desc_table: db 09h db 02h db 22h,00h db 01h db 01h db 00h db 80h db 32h ; ; ; ; ; ; ; ; Interface_Descriptor: db 09h ; db 04h ; db 00h ; db 00h ; db 01h ; db 03h ; db 00h ; db 00h ; db 00h ; Descriptor length (18 bytes) Descriptor type (Device) Complies with USB Spec. Release (0110h = release 1.10) Class code (0) Subclass code (0) Protocol (No specific protocol) Max. packet size for EP0 (8 bytes) Vendor ID (Lakeview Research, 0925h) Product ID (1234) Device release number (0001) Mfr string descriptor index Mfr string descriptor index (None) Product string descriptor index Serial Number string descriptor index (None) Number of possible configurations (1) Descriptor length (9 bytes) Descriptor type (Configuration) Total data length (34 bytes) Interface supported (1) Configuration value (1) Index of string descriptor (None) Configuration (Bus powered) Maximum power consumption (100mA) Descriptor length (9 bytes) Descriptor type (Interface) Number of interface (0) Alternate setting (0) Number of interface endpoint (1) Class code () Subclass code () Protocol code () Index of string() Class_Descriptor: db 09h db 21h db 00h,01h db 00h db 01h db 22h ; Descriptor length (9 bytes) ; Descriptor type (HID) ; HID class release number (1.00) ; Localized country code (None) ; # of HID class dscrptr to follow (1) ; Report descriptor type (HID) ; Total length of report descriptor db (end_hid_report_desc_table - hid_report_desc_table),00h Endpoint_Descriptor: db 07h db 05h db 81h db 03h db 06h,00h db 0Ah ; ; ; ; ; ; end_config_desc_table: Descriptor length (7 bytes) Descriptor type (Endpoint) Encoded address (Respond to IN, 1 endpoint) Endpoint attribute (Interrupt transfer) Maximum packet size (6 bytes) Polling interval (10 ms) ;---------------------------------------------------------------------;The HID-report descriptor table ;---------------------------------------------------------------------hid_report_desc_table: db 06h, A0h, FFh db 09h, 01h db A1h, 01h db 09h, 02h db A1h, 00h db 06h, A1h, FFh ; ; ; ; ; ; ;The output report db 09h, 05h db 09h, 06h db 15h, 80h db 25h, 7Fh db 35h, 00h db 45h, FFh db 75h, 08h db 95h, 0Bh db 91h, 02h ; ; ; ; ; ; ; ; ; db C0h db C0h end_hid_report_desc_table: ; ; Usage Page (vendor defined) FFA0 Usage (vendor defined) Collection (Application) Usage (vendor defined) Collection (Physical) Usage Page (vendor defined) usage - vendor defined usage - vendor defined Logical Minimum (-128) Logical Maximum (127) Physical Minimum (0) Physical Maximum (255) Report Size (8) (bits) Report Count (11) (fields) Output (Data, Variable, Absolute) End Collection End Collection