Depuração e programação de microcontroladores stm32f303, atmega328 por meio de qualquer interface, como por meio de jtag

Este artigo é sobre meu primeiro projeto de código aberto "repl" (link para o repositório abaixo). A ideia deste projeto é permitir que o programador do microcontrolador depure o programa no microcontrolador por meio de qualquer uma de suas interfaces, enquanto a depuração não difere muito da depuração por meio da interface jtag. Era possível parar o programa, definir pontos de interrupção, visualizar registros, memória, por depuração instrutiva do programa.



A primeira coisa que me vem à cabeça é criar uma aplicação 2x, uma das threads é responsável pela interface de depuração, a outra pelo programa do usuário, o que eu fiz. A alternância entre threads é realizada em um cronômetro, cada thread tem sua própria pilha. Decidi não usar muitos para escrever a interface de depuração. eles devem ser usados ​​2 diferentes, ou ao trabalhar com um heap, alternar constantemente para um segmento.



A primeira ideia para a implementação da depuração instrucional era reduzir o tempo entre as interrupções do temporizador apenas o suficiente para que apenas 1 instrução pudesse ser executada. Esta opção mostrou seu funcionamento ideal no microcontrolador Atmega328p, o fato é que o tempo mínimo entre interrupções para o Atmega é de 1 ciclo do processador, qualquer instrução, independente do número de ciclos necessários para sua execução, sempre terminará se sua execução for iniciada.



Infelizmente, quando mudei para stm32, esta opção não funcionou, pois o núcleo Cortex-M pode interromper a execução de uma instrução no meio e, ao retornar da interrupção, começar a executá-la novamente. Então decidi bem, sem problemas, vou apenas aumentar o número de ciclos de clock entre as interrupções até que a instrução seja executada, então me deparei com outro problema, algumas instruções são executadas apenas com a instrução seguinte ou não são executadas, infelizmente este problema não pode ser resolvido, então decidi mudar radicalmente a abordagem.



Decidi emular essas instruções. À primeira vista, a ideia de escrever um emulador de núcleo Cortex M, e até mesmo um que caberia na memória do microcontrolador, parece fantástica. Mas percebi que não precisava emular todas as instruções, mas apenas aquelas que estão associadas ao contador do programa, não havia tantas. Todo o resto eu posso simplesmente mover para outro local de memória e executar lá, e para que apenas uma instrução seja executada, adicionar a instrução de salto “b” depois dela.



E então chegou a vez dos pontos de interrupção, para sua implementação decidi usar uma instrução que implementa a lógica while (1) ;. Substituímos a instrução localizada no endereço onde queremos definir o ponto de interrupção e esperamos pela interrupção do temporizador. Eu entendo que algo como uma instrução que lançaria uma exceção seria melhor, mas eu queria fazer uma versão universal. Quando executada, substituímos essa instrução de volta. Uma boa opção é executar o programa na RAM do microcontrolador, caso contrário a memória flash do microcontrolador não durará muito. Mas neste ponto eu já tinha terminado de escrever um emulador de instrução para stm32 e decidi por que não escrever o mesmo para Atmega328 e escrevi. Agora você não precisa substituir as instruções de volta; elas podem ser emuladas.



Para tornar tudo isso amigo do tempo de execução, primeiro eu quis escrever meu próprio cliente gdb. Infelizmente, ele suporta duas interfaces para trabalhar com ide. Cabe a ela decidir qual ide usar Implementar os dois (o primeiro me parecia bastante simples, o segundo não muito), além disso, eu teria que combinar as fontes com o firmware, o que não me pareceu uma ideia muito boa. Então decidi escrever meu próprio gdbserver, felizmente havia apenas um protocolo e era bem simples.



Minha primeira interface, que decidi implementar, foi o GSM. Como transceptor, decidi usar o SIM800, como servidor, um servidor com suporte a php. A ideia principal era que após a chegada de um post request do microcontrolador, manter a conexão com o microcontrolador por 30 segundos e contatar o banco de dados a cada 100 ms, caso os dados aparecessem, enviar em resposta ao pedido e esperar a próxima solicitação do microcontrolador.



A primeira conexão do cliente gdb ao servidor mostrou que havia muitas solicitações do cliente gdb para o servidor com o comando pause ou step. Portanto, foi decidido combinar todas essas solicitações em um grande para o microcontrolador, para isso eu entendi a lógica dessas solicitações e aprendi como prevê-las. Agora esses comandos não foram executados tão rapidamente, eu gostaria mais rápido, mas suportável.



A próxima interface foi usb, para o microcontrolador Atmega328 decidi usar a biblioteca V-usb. Para trabalhar com usb, reescrevi a lógica do comando de execução. Agora, o microcontrolador após este comando não iniciou o programa esperando pelo comando de pausa, ele o iniciou por 1 segundo, em seguida, um novo comando de execução foi enviado a ele, etc. Essa lógica é necessária porque Eu desabilito a interface enquanto o programa está em execução. Para evitar o incômodo de escrever um driver, decidi usar o driver oculto padrão. Como os recursos de comunicação ocultaram o relatório de recurso, o relatório de recurso foi definido.



Quanto ao piscar dos microcontroladores, decidi que isso é supérfluo para a interface usb, então para o primeiro firmware você ainda precisa de um programador. Mas para a interface GSM é o máximo. Normalmente, um programa separado é escrito para essa finalidade, mas decidi fazer de forma diferente, em vez de escrever um programa separado, decidi carregar o programa completamente na memória flash do microcontrolador, então após o download estar completo, copie este programa para o início da memória. Então pensei porque devo enviar o programa inteiro, só posso enviar a diferença entre o arquivo binário atual e o anterior do firmware.



Para minimizar essa diferença, decidi renomear as seções do array .text, .data, .bss, .contructors para uma parte do programa do usuário (o nome generalizado é diferente para microcontroladores diferentes) e colocá-los na memória logo após o programa principal.



Eu também tive que escrever minhas próprias funções para inicializar essas seções. Agora, na maioria dos casos, uma pequena mudança no programa é igual a uma pequena mudança binária igual a uma pequena quantidade de dados sendo transferidos. Como resultado, o microcontrolador costuma piscar mais rápido do que os comandos RUN, STEP e PAUSE funcionam.



E por último, um vídeo de trabalho:



depuração Stm32 via interface usb.





Depuração Stm32 via interface gsm.





Depuração Atmega328 via interface USB.





Depuração Atmega328 via interface gsm.





Repositório Git



All Articles