Depuración y programación de microcontroladores stm32f303, atmega328 a través de cualquier interfaz, como a través de jtag

Este artículo trata sobre mi primer proyecto de código abierto "repl" (enlace al repositorio a continuación). La idea de este proyecto es permitir que el programador del microcontrolador depure el programa en el microcontrolador a través de cualquiera de sus interfaces, mientras que la depuración no difiere mucho de la depuración a través de la interfaz jtag. Fue posible detener el programa, establecer puntos de interrupción, ver registros, memoria, mediante la depuración instructiva del programa.



Lo primero que me viene a la mente es crear una aplicación 2x, uno de los hilos se encarga de la interfaz de depuración, el otro del programa de usuario, lo cual hice. El cambio entre subprocesos se realiza en un temporizador, cada subproceso tiene su propia pila. Decidí no usar un montón de herramientas para escribir una interfaz de depuración. deben usarse 2 diferentes, o cuando se trabaja con un montón, cambiar constantemente a un hilo.



La primera idea para la implementación de la depuración instruccional fue reducir el tiempo entre las interrupciones del temporizador lo suficiente para que solo se pudiera ejecutar una instrucción. Esta opción mostró su funcionamiento ideal en el microcontrolador Atmega328p, lo cierto es que el tiempo mínimo entre interrupciones para Atmega es de 1 ciclo de procesador, cualquier instrucción, independientemente de la cantidad de ciclos requeridos para su ejecución, siempre terminará si su ejecución ha comenzado.



Desafortunadamente, cuando cambié a stm32, esta opción no funcionó, porque el núcleo Cortex-M puede interrumpir la ejecución de una instrucción en el medio y luego, al regresar de la interrupción, comenzar a ejecutarla nuevamente. Entonces decidí bien, sin problemas, solo aumentaré el número de ciclos de reloj entre interrupciones hasta que se ejecute la instrucción, luego encontré otro problema, algunas instrucciones se ejecutan solo con la instrucción que las sigue o no se ejecutan en absoluto, desafortunadamente este problema no se puede resolver, así que decidí cambiar radicalmente el enfoque.



Decidí emular estas instrucciones. A primera vista, la idea de escribir un emulador de núcleo Cortex M, e incluso uno que quepa en la memoria del microcontrolador, parece fantástica. Pero me di cuenta de que no necesitaba emular todas las instrucciones, sino solo aquellas asociadas con el contador del programa, no había tantas. Todo lo demás, simplemente puedo moverme a otra ubicación de memoria y ejecutar allí, y para que solo se ejecute una instrucción, agregue la instrucción de salto "b" después de ella.



Y luego llegó el turno de los breakpoints, para su implementación decidí usar una instrucción que implemente la lógica while (1); Reemplazamos la instrucción ubicada en la dirección donde queremos establecer el punto de interrupción y esperamos la interrupción del temporizador. Entiendo que sería mejor algo como una instrucción que arrojara una excepción, pero quería hacer una versión universal. Cuando se ejecuta, reemplazamos esta instrucción. Una buena opción es ejecutar el programa en la RAM del microcontrolador, de lo contrario la memoria flash del microcontrolador no durará mucho. Pero en este punto ya había terminado de escribir un emulador de instrucciones para stm32 y decidí por qué no escribir el mismo para Atmega328 y escribirlo. Ahora no es necesario que vuelva a colocar las instrucciones; se pueden emular.



Para hacer todos estos amigos con el tiempo de ejecución, primero quería escribir mi propio cliente gdb. Desafortunadamente, admite dos interfaces para trabajar con ide. Cómo utilizar ide decide por sí mismo. Implementando ambos (el primero me pareció bastante sencillo, el segundo no muy), además tendría que combinar las fuentes con el firmware, lo que no me pareció muy buena idea. Así que decidí escribir mi propio gdbserver, afortunadamente solo había un protocolo y era bastante simple.



Mi primera interfaz, que decidí implementar, fue GSM. Como transceptor, decidí usar SIM800, como servidor, un servidor con soporte php. La idea principal era que luego de la llegada de una post solicitud del microcontrolador, mantuviera la conexión al microcontrolador durante 30 segundos y contactara con la base de datos cada 100 ms, si aparecían los datos enviarlo como respuesta a la solicitud y esperar la siguiente solicitud del microcontrolador.



La primera conexión del cliente gdb al servidor mostró que había demasiadas solicitudes del cliente gdb al servidor con el comando pause o step. Por lo tanto, se decidió combinar todas estas solicitudes en una grande para el microcontrolador, para esto entendí la lógica de estas solicitudes y aprendí a predecirlas. Ahora bien, estos comandos no se ejecutaron tan rápido, me gustaría más rápido, pero soportable.



La siguiente interfaz fue usb, para el microcontrolador Atmega328 decidí usar la biblioteca V-usb. Para trabajar con usb, reescribí la lógica del comando de ejecución. Ahora, el microcontrolador después de este comando no inició el programa esperando el comando de pausa, lo inició durante 1 segundo, luego se le envió un nuevo comando de ejecución, etc. Esa lógica es necesaria porque Desactive la interfaz mientras se ejecuta el programa. Para ahorrarme la molestia de escribir un controlador, decidí utilizar el controlador hid estándar. Como las funciones de comunicación ocultaron obtener el informe de funciones, ocultaron el informe de funciones establecidas.



En cuanto al flasheo de los microcontroladores, decidí que esto es superfluo para la interfaz usb, por lo que para el primer firmware todavía necesita un programador. Pero para la interfaz GSM es lo más. Por lo general, se escribe un programa separado para este propósito, pero decidí hacerlo de manera diferente, en lugar de escribir un programa separado, decidí cargar el programa completamente en la memoria flash del microcontrolador, luego, una vez completada la descarga, copie este programa al principio de la memoria. Entonces pensé por qué debería enviar el programa completo, solo puedo enviar la diferencia entre el archivo binario actual y el anterior del firmware.



Para minimizar esta diferencia, decidí cambiar el nombre de las secciones del arreglo .text, .data, .bss, .contructors para una parte del programa de usuario (el nombre generalizado es diferente para diferentes microcontroladores) y colocarlos en la memoria justo después del programa principal.



También tuve que escribir mis propias funciones para inicializar estas secciones. Ahora, en la mayoría de los casos, un pequeño cambio de programa es igual a un pequeño cambio binario igual a una pequeña cantidad de datos transferidos. Como resultado, el microcontrolador a menudo parpadea más rápido de lo que funcionan los comandos RUN, STEP, PAUSE.



Y finalmente, un video de trabajo:



Stm32 depurando vía interfaz usb.





Depuración de Stm32 a través de la interfaz gsm.





Depuración de Atmega328 a través de la interfaz usb.





Depuración de Atmega328 a través de la interfaz gsm.





Repositorio de Git



All Articles