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,
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 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
Nenhum comentário:
Postar um comentário