Estou pensando em projetos para um sistema de jogo minimalista baseado em um PIC18F85J5. Parte do meu design é que os jogos podem ser carregados a partir de um cartão SD sem reprogramar o chip ou piscar a memória do programa. Eu escolhi esse chip porque ele tem uma interface de memória externa que me permitirá executar o código de uma SRAM externa.
A idéia básica é que a memória interna do programa contenha uma interface para navegar no cartão sd e, uma vez que o usuário selecione um programa, ele copiará um arquivo hexadecimal do cartão sd para a ram externa e, em seguida, saltará a execução para o espaço externo da ram. .
A memória interna do programa também terá várias bibliotecas para gráficos, entrada do controlador e outros vários utilitários.
Estou bastante confiante de que sei como fazer com que as partes internas do firmware funcionem bem. O problema está criando programas para serem executados a partir da RAM externa. Ele não parece o mesmo que direcionar uma foto normal e precisa estar ciente das funções da biblioteca que estão disponíveis na memória interna, mas não recompilá-las, apenas vinculá-las. Ele também precisa começar a usar endereços logo após os 32k de flash interno, e não em zero. Existe uma boa maneira de compilar um programa usando esses tipos de restrições?
Estou usando o MPLab IDE, mas não estou muito familiarizado com ele ou como fazer esse tipo de personalização.
Respostas:
Você tem dois problemas separados:
Isso é realmente muito fácil. Tudo o que você precisa fazer é criar um arquivo vinculador que contenha apenas os intervalos de endereços que você deseja que o código ocupe. Observe que você não precisa apenas reservar um intervalo de endereços de memória de programa específico para esses aplicativos externos, mas também algum espaço de RAM. Esse espaço de RAM, como os endereços de memória do programa, precisa ser corrigido e conhecido. Simplesmente disponibilize apenas os intervalos de endereços fixos e conhecidos disponíveis para uso no arquivo vinculador. Não se esqueça de torná-los NÃO disponíveis no arquivo vinculador de código base.
Depois que o código base carrega um novo aplicativo na memória externa, ele precisa saber como executá-lo. O mais fácil é provavelmente iniciar a execução no primeiro local de RAM externa. Isso significa que seu código precisará de uma seção CODE nesse endereço inicial absoluto. Isso contém um GOTO para o rótulo correto no restante do código, que será realocável.
Não existe uma maneira imediatamente simples de fazer isso com as ferramentas de microchip existentes, mas também não é tão ruim assim.
Uma questão muito maior é como você deseja lidar com as alterações no código base. A estratégia simplista é construir seu código base, executar um programa sobre o arquivo de mapa resultante para coletar endereços globais e depois gravar um arquivo de importação com instruções EQU para todos os símbolos definidos globalmente. Esse arquivo de importação seria incluído em todo o código do aplicativo. Não há nada a vincular, pois o código fonte do aplicativo contém essencialmente as referências de endereço fixo aos pontos de entrada do código base.
Isso é fácil de fazer e funcionará, mas considere o que acontece quando você altera o código base. Mesmo uma pequena correção de bug poderia fazer com que todos os endereços se movessem e, então, todo o código de aplicativo existente não seria bom e precisaria ser reconstruído. Se você nunca planeja fornecer atualizações de código base sem atualizar todos os aplicativos, talvez possa se safar disso, mas acho que é uma má ideia.
Uma maneira melhor é ter uma área de interface definida em um endereço conhecido fixo escolhido no código base. Haveria um GOTO para cada sub-rotina que o código do aplicativo pode chamar. Esses GOTOs seriam colocados em endereços conhecidos fixos, e os aplicativos externos apenas chamariam para esses locais, que iriam para onde a sub-rotina realmente terminasse na compilação do código base. Isso custa 2 palavras de memória de programa por sub-rotina exportada e dois ciclos extras em tempo de execução, mas acho que vale a pena.
Para fazer isso corretamente, você precisa automatizar o processo de geração dos GOTOs e o arquivo de exportação resultante que os aplicativos externos importarão para obter os endereços da sub-rotina (na verdade, o redirecionador GOTO). Você pode se familiarizar com o uso inteligente de macros MPASM, mas, se estivesse fazendo isso, definitivamente usaria meu pré-processador, pois ele pode gravar em um arquivo externo no momento do pré-processamento. Você pode escrever uma macro de pré-processador para que cada redirecionador possa ser definido por uma única linha de código-fonte. A macro faz todas as coisas desagradáveis sob o capô, que são gerar o GOTO, a referência externa à rotina de destino real e adicionar a linha apropriada ao arquivo de exportação com o endereço constante conhecido dessa rotina, todos com nomes apropriados. Talvez a macro crie apenas um monte de variáveis de pré-processador com nomes regulares (como uma matriz expansível em tempo de execução) e, em seguida, o arquivo de exportação seja gravado uma vez depois de todas as chamadas de macro. Uma das muitas coisas que meu pré-processador pode fazer que as macros MPASM não podem fazer é a manipulação de cadeias para criar novos nomes de símbolos a partir de outros nomes.
Meu pré-processador e várias outras coisas relacionadas estão disponíveis gratuitamente em www.embedinc.com/pic/dload.htm .
fonte
Opção 1: Idiomas Interpretados
Isso não responde diretamente à pergunta (que é uma excelente pergunta, BTW, e espero aprender com uma resposta que a aborda diretamente), mas é muito comum ao executar projetos que podem carregar programas externos para escrever programas externos em uma linguagem interpretada. Se os recursos forem escassos (que estarão neste processador, você já pensou em usar um PIC32 ou um pequeno processador ARM para isso?), É comum restringir o idioma a um subconjunto da especificação completa. Ainda mais abaixo, estão linguagens específicas de domínio que fazem apenas algumas coisas.
Por exemplo, o projeto elua é um exemplo de uma linguagem interpretada com poucos recursos (64 kB de RAM). Você pode compactar isso para 32k de RAM se remover alguns recursos (Nota: ele não funcionará no seu processador atual, que é uma arquitetura de 8 bits. O uso de RAM externa provavelmente será muito lento para gráficos). Ele fornece uma linguagem rápida e flexível na qual novos usuários podem programar jogos facilmente se você fornecer uma API mínima. Há muita documentação disponível para o idioma online. Existem outras linguagens (como Forth e Basic) que você pode usar de maneira semelhante, mas acho que Lua é a melhor opção no momento.
Da mesma forma, você pode criar seu próprio idioma específico do domínio. Você precisaria fornecer uma API mais completa e documentação externa, mas se os jogos fossem todos semelhantes, isso não seria muito difícil.
De qualquer forma, o PIC18 provavelmente não é o processador que eu usaria para algo que envolve programação / script e gráficos personalizados. Você pode estar familiarizado com essa classe de processadores, mas eu sugiro que esse seja um bom momento para usar algo com um driver de vídeo e mais memória.
Opção 2: basta reprogramar a coisa toda
Se, no entanto, você já planeja programar todos os jogos em C, não se preocupe em carregar apenas a lógica do jogo no cartão SD. Você tem apenas 32kB de Flash para reprogramar e pode facilmente obter um cartão microSD de 4 GB para isso. (Nota: cartões maiores costumam ser SDHC, o que é mais difícil de interface). Supondo que você use cada último byte dos seus 32 kB, isso deixa espaço no cartão SD para 131.072 cópias do seu firmware com a lógica do jogo que você precisa.
Existem várias notas de app para escrever gerenciadores de inicialização para PICs, como o AN851 . Você precisaria projetar seu carregador de inicialização para ocupar uma região específica da memória (provavelmente a parte superior da região da memória, você especificaria isso no vinculador) e especificar que os projetos completos de firmware não atinjam essa região. A nota explicativa explica isso em mais detalhes. Apenas substitua "Seção de inicialização do PIC18F452" por "Seção de inicialização especificada no vinculador" e tudo fará sentido.
Em seguida, o seu carregador de inicialização precisa apenas permitir que o usuário selecione um programa para executar no cartão SD e copie tudo. Uma interface do usuário pode ser que o usuário mantenha pressionado um botão para entrar no modo de seleção. Normalmente, o gerenciador de inicialização apenas verifica o status desse botão na redefinição e, se não estiver sendo pressionado, inicia o jogo. Se mantida pressionada, seria necessário permitir ao usuário escolher um arquivo no cartão SD, copiar o programa e continuar a inicializar no [novo] jogo.
Esta é a minha recomendação atual.
Opção 3: Magia profunda envolvendo o armazenamento de apenas parte do arquivo hexadecimal
O problema com o mecanismo previsto é que o processador não lida com APIs e chamadas de função, lida com números - endereços para os quais o ponteiro de instruções pode saltar e espera que haja código que execute uma chamada de função de acordo com uma especificação da API. Se você tentar compilar apenas uma parte do programa, o vinculador não saberá o que fazer quando você ligar
check_button_status()
outoggle_led()
. Você pode saber que essas funções existem no arquivo hexadecimal do processador, mas precisa saber exatamente em que endereço elas residem.O vinculador já divide seu código em várias seções; teoricamente, você poderia dividir isso em seções adicionais com alguns
-section
e#pragma
encantamentos. Eu nunca fiz isso e não sei como. Até que os dois métodos acima falhem comigo (ou alguém poste uma resposta incrível aqui), provavelmente não aprenderei esse mecanismo e, portanto, não posso ensiná-lo.fonte