terça-feira, 6 de agosto de 2013

Atualização do projeto de emulador de interface de disquete (#3)


https://lh3.googleusercontent.com/-DwF4ZPovNJ0/UfVVtkbfcFI/AAAAAAAAAKU/fZa3fji6EKA/s1600/Proto1.jpg


Olá,

Eu fiquei um período relativamente grande sem postar nada porque eu estava tendo uma série de dificuldades com o projeto do emulador. Acima tem as imagens da fiação no meu protoboard com a EEPROM, o PIC, o display de LCD e o CPLD instalados. Quase não dá pra ver a eeprom e o PIC de tanto fio em torno dos componentes, mas está tudo funcionando até agora. Ainda falta instalar no protoboard o cartão SD e as quatro teclas de controle.

Depois que eu descobri que o PIC18F4550 possui pinos conflitantes entre os dispositivos EUSART e MSSP eu decidi que não mais usaria um PIC no módulo de controle externo para controlar o display de LCD e as teclas de controle. Eu tinha tomado esta decisão inicialmente com o objetivo de diminuir o "overhead" sobre o PIC principal que faz a comunicação diretamente com o barramento do MSX. Desta forma a topologia do projeto mudou e o PIC agora controla as teclas de controle e o display de LCD. Eu também estava pensando em trocar o PIC por outro modelo mais moderno com mais memória e com dispositivos EUSART e MSSP não conflitantes mas decidi em insistir no mesmo PIC. Abaixo segue a imagem com a nova topologia.

Com a topologia num estágio mais maduro eu fui capaz de finalmente decidir qual será a função dos pinos do PIC. Ou seja, eu consegui distribuir o que cada pino do PIC fará.. A tabela abaixo ainda não é definitiva mas está bem próxima do que cada pino fará. Talvez eu use o pino RE3/MCLR para detecção de pressionamento de uma tecla e use um outro pino do PIC para acender e apagar um LED. O LED seria usado para indicar a presença do cartão SD no slot e também para indicar que está havendo alguma atividade no cartão. Da seguinte forma: 1) Led Apagado - Sem cartão SD, 2) Led Acesso - Com cartão SD e 3) Led piscando - O cartão SD está sendo acessado. Eu ainda não me decidi completamente porque me parece que o LED seria uma redundância desnecessária já que eu terei um LCD com as informações que o LED me dará e muito mais.


O primeiro passo... e o primeiro problema... foi para inserir o código para tratar o LCD. A PLIB da Microchip oferece o xlcd que teoricamente deveria funcionar bem com o meu display mas por algum motivo que eu desconheço não funcionou ou eu não fui capaz de fazer as funções funcionarem para mim. Então eu criei as minhas próprias funções para tratamento do LCD. Eu criei um função putch para permitir que eu use a função printf em conjunto com o LCD também. Abaixo segue uma imagem com o display funcionando. Eu usei o printf para escrever o texto abaixo e na segunda linha eu "printei" alguns caracteres customizados que eu criei para usar com o emulador. O primeiro caractere seria um disquete (não acho que ficou muito parecido com um). O segundo caractere uma pasta (também não sei se ficou muito parecida). O terceiro e o quarto representam um cadeado aberto e fechado respectivamente. E os demais caracteres seriam setas para cima e para baixo que serão usados para a navegação dos arquivos dentro do cartão SD. O disquete seria para indicar que o arquivo sendo visto é um arquivo .dsk válido e a pasta para indicar que na verdade o que estamos vendo é um pasta de trabalho. Talvez eu crie um caractere especial em formato de coração para marcar um arquivo .dsk para ser carregado por default ao ligar a interface.







O segundo passo foi fazer o PIC finalmente comunicar com o MSX via barramento. Eu estou disponibilizando todo o código na minha pasta pública do Dropbox e o link segue no final da mensagem mas eu estou listando o código da rotina de tratamento da interrupção do PIC abaixo porque me deu muito trabalho e gostaria de falar um pouco mais em detalhe sobre ela. A rotina abaixo é o coração de todo o projeto, simplesmente do seu funcionamento depende todo o restante do projeto. Se eu não conseguisse fazer uma rotina rápida o suficiente para atender as requisições do barramento do MSX o projeto se tornaria inviável. Mas posso adiantar que eu consegui.

#include "xc.inc"

global _total_bus_read, _total_bus_write, _total_bus_readwrite
global _temp_FSR0L,_temp_FSR0H
global MSXBUSIntRead
global MSXBUSIntWrite

PSECT highintvec,class=CODE,abs,
ovrld
            org 0x10
HighIntStart:
            movff   FSR0L,_temp_FSR0L
            movff   FSR0H,_temp_FSR0H
            clrf    FSR0H
            movf    PORTB,w
            andlw   0xf0
            swapf   WREG
            movwf   FSR0L
            incf    INDF0
            bsf     FSR0L,4
            btfsc   PORTB,PORTB_RB7_POSITION
            goto    MSXBUSIntRead
            goto    MSXBUSIntWrite

PSECT text,class=CODE,ovrld

MSXBUSIntRead:
            movff   INDF0,PORTD
            clrf    TRISD
            bra     wait_bus_r

MSXBUSIntWrite:
            movff   PORTD,INDF0

            banksel _total_bus_write
            incf    _total_bus_write
            bnc     wait_bus
            incf    _total_bus_write+1
            bra     wait_bus

wait_bus_r:
            banksel _total_bus_read
            incf    _total_bus_read
            bnc     wait_bus
            incf    _total_bus_read+1

wait_bus:
            movf    PORTB,w
            andlw   0xf0
            xorlw   0xf0
            bnz     wait_bus
                       
            setf    TRISD
            banksel _total_bus_readwrite
            incf    _total_bus_readwrite
            bnc     end_int
            incf    _total_bus_readwrite+1
end_int:
            bcf     INTCON,INTCON_RBIF_POSITION
            movff   _temp_FSR0L,FSR0L
            movff   _temp_FSR0H,FSR0H
            retfie  f
            end

Inicialmente eu gostaria de falar que eu já tinha programado em assembler para PIC (PIC12F e PIC16F) e que fazia um tempo longo que eu não tinha programado nada em assembler. E com este projeto eu descobri como eu estou literalmente enferrujado para programar em assembler. Foi um verdadeiro pesadelo criar esta rotina simples e eu acho que o ASPIC18, que é o compilador assembler que acompanha o XC8, tem várias e várias armadilhas para quem tá começando. Boa parte da minha dificuldade foi em descobrir o que o compilador e o linker estavam fazendo com o meu código. Uma dificuldade adicional é o fato de que o compilador gera pouquíssimas mensagens de erro o que dificulta bastante a depuração. Isto posto vou agora voltar a descrição do projeto.

O barramento de dados do MSX está ligado diretamente nos pinos RD0..RD7 do PIC e os pinos de seleção do CPLD estão ligados nos pinos RB4..RB7. Uma descrição mais detalhada de como funciona os pinos de seleção está na mensagem #2. O importante é saber que caso seja realizado uma operação de IO dentro dos endereços válidos o estado lógico dos pinos RB4..RB7 serão alterados e será gerada no PIC uma "interrupt-on-change" que foi dada alta prioridade. A idéia é que nenhuma outra fonte de interrupção no PIC gera interrupções de alta prioridade a não ser a "interrupt-on-change" com isto eu diminuo a quantidade de código no vetor de alta prioridade e aumento a velocidade no tratamento da interrupção.

A primeira parte do código incrementa um contador de acessos de registro. O que isto significa? Significa o seguinte: Imagine que o MSX faça um acesso de escrita de IO no endereço D1 a primeira parte de código incrementará o valor contido no endereço 0x01 da memória do PIC. Já se o MSX fizer um acesso de leitura de IO no endereço D2 a primeira parte do código incrementará o valor contido no endereço 0x0a da memória do PIC. O objetivo disto é para termos uma forma de descobrir qual registro foi lido ou escrito pelo MSX. A idéia é que estes registros por default tenham valor 0 e a rotina principal do PIC faça com regularidade uma varredura destes contadores, caso um ou mais destes contadores não possua valor 0 a rotina principal tratará o registro de acordo com a situação e zerará o contador novamente. Caso o contador possua um valor maior que 1 significa que o MSX fez dois ou mais acessos ao registro sem que a rotina principal a tenha processado o que, dependendo da situação, pode ou não ser uma situação de erro.

Depois do código inicial a rotina se divide em duas partes. Uma parte que trata as leituras de barramento e a outra que trata as escritas. No caso das escritas, o código lê o valor contido no PORTD e armazena este valor no registro correto. No caso de leituras, o registro correto é escrito na PORTD e a PORTD é configurada como saída (o estado normal da PORTD é estar configurada como entrada). Há um conjunto especifico de registros no PIC para leituras e um conjunto específico para as escritas. Desta forma é possível o MSX escrever um valor em um endereço de IO e imediatamente após ler um outro valor no mesmo endereço de IO. Isto na verdade está em acordo com o que faz o CI controlador de disquete WD2793 que estou tentando emular que no endereço 0 você escreve um comando mas no mesmo endereço você lê o status do chip. Até este momento o código é crítico e precisa ser o mais rápido possível. Eu me esforcei bastante em diminuir ao máximo a quantidade de instruções nesta parte da rotina e nos meus cálculos  eu consigo atender o barramento do MSX com 21 ciclos de máquina do PIC ou seja, consigo atender o barramento em 1,75 us com o PIC rodando a 12 MIPS. É uma pena que não tenha como reservar o uso dos registros de indexação FSRx para a rotina de interrupção. Eu conseguiria economizar 5 ciclos de máquina do PIC simplesmente se eu não tivesse que salvar o estado de FS0L e FS0H e se não tivesse que iniciar o FS0H toda vez que a interrupção roda. Mas fazer o que?

A parte final da rotina incrementa uns contadores de 16 bits que eu estou usando para fazer testes mas que na versão final serão retirados e há um loop em que espera os pinos RB4..RB7 voltarem para seu estado default, i.e., todos os pinos com nível lógico 1, para finalmente restaurar o estado de FS0RL e FS0RH e terminar a interrupção. O interessante é que durante a inicialização do MSX com a BIOS do CDX2 os registros relativos ao disco são sempre acessados o mesmo número de vezes. Durante meus testes eu fiz uma rotina que de tempos em tempos amostra no LCD a quantidade de vezes que foi realizado operações de escrita ou leitura nos endereços de IO de D0 até D7. A foto abaixo foi tirada após a inicialização do meu MSX com a BIOS CDX2.

Na imagem acima o barramento de dados ainda não tinha sido ligado ao PIC. Depois que eu liguei o barramento de dados ao PIC a rotina de inicialização trava na inicialização tentando ler milhares de vezes os endereços de IO, muito interessante. Eu agora tenho que ligar o MSX com o PIC desligado para só então ligar o PIC. Isto para evitar o travamento durante a inicialização. Provavelmente o valor 0xff no registrador de status faz com que a rotina de inicialização da bios do CDX2 entre em loop constante. Como ainda não há uma rotina que trate adequadamente os registros o valor nunca muda.


https://lh3.googleusercontent.com/-c9SCK8DdHKc/UfV6SWEYjsI/AAAAAAAAALo/y7Le6S6x7pU/s1600/PIC+Entrando+em+a%C3%A7%C3%A3o.jpgNa Imagem ao lado podemos ver o PIC entrando em ação. No MSX foi gerado uma leitura no endereço de IO D0. A linha amarela é a linha de endereço A2 sendo monitorada no pino RB6 do PIC e a linha azul é a linha de dados D0. Podemos observar que um pouco antes da descida da linha de endereço do Z80 a tensão do barramento de dados começa a flutuar porque nem o Z80 e nem o PIC está comandando o barramento. No momento que a linha de endereço desce o "interrupt-on-change" entra em ação e a rotina de interrupção começa a tratar o barramento. Podemos ver o momento exato em que o TRISD é zerado e o barramento de dados se torna uma saída escrevendo o valor lógico 1 na linha de dados D0. Todo o intervalo desde a descida da linha de endereço até que linha de dados passe a ser controlada pelo PIC é o tempo de processamento da rotina de interrupção descrita anteriormente. A linha de endereço só se mantém ativa todo este período graças aos 7 wait states que estou inserindo. Eu ainda não testei mas é provavel que com 5 ou 6 wait states o circuito continue funcionando. Mas de qualquer forma ficou claro que é muito importante o uso de wait states para o meu emulador.

Mais a frente eu devo alterar o CPLD para que multiplique o número de wait states sendo gerado por 2 ou por 3. De forma que se os pinos  WS_SEL  possuir o valor 7 por exemplo seja gerado 14 ou até mesmo 21 ciclos de espera. Desta forma espero tornar o meu projeto compatível com computadores mais rápidos como por exemplo o Turbo-R.

Na imagem acima é a mesma configuração dos sinais da imagem anterior só que desta vez é uma operação de escrita aonde está sendo escrito o valor 0 no endereço de IO D0. Podemos observar que o Z80 baixa a linha de dados um pouco antes de baixar a linha de endereço e que ele levanta a linha de dados um pouco depois de levantar a linha de endereço.

Por hoje é só. Estou mega cansado e me sentindo um pouco estafado mas feliz com o progresso do projeto. Espero que as informações que estou colocando aqui seja de alguma utilidade para alguém.
Conforme prometido segue o link para o arquivo contendo todo o código do PIC até agora. Eu vou estudar uma forma de colocar o código num servidor de svn para que, quem quiser, possa contribuir também para o projeto. Se alguém tiver alguma sugestão ou crítica sou todo ouvidos.


Um abraço,

José Paulo