Hola amigosEn artículos anteriores,
hablé sobre mi proyecto y
su parte de software . En este artículo, le diré cómo hacer un generador de señal simple para 4 canales: dos canales analógicos y dos canales PWM.

Canales análogos
El microcontrolador
STM32F415RG incorpora un convertidor
DAC (digital a analógico) de 12 bits en dos canales independientes, lo que permite generar diferentes señales. Puede cargar datos directamente en los registros del convertidor, pero esto no es muy adecuado para generar señales. La mejor solución es usar una matriz en la que generar una onda de la señal y luego ejecutar el DAC con un disparador del temporizador y DMA. Al cambiar la frecuencia del temporizador, puede cambiar la frecuencia de la señal generada.
Las formas de onda "
clásicas " incluyen: sinusoidal, meandro, triangular y diente de sierra.

La función de generar estas ondas en el búfer es la siguiente En la función, debe pasar un puntero al comienzo de la matriz, el tamaño de la matriz, el valor máximo y la forma de onda deseada. Después de la llamada, la matriz se llenará con muestras para una onda de la forma requerida y puede iniciar el temporizador para cargar periódicamente el nuevo valor en el DAC.
El DAC en este microcontrolador tiene una limitación: el tiempo de establecimiento típico (el
tiempo desde la carga de un nuevo valor en el DAC y cuando aparece en la salida ) es de 3 ms. Pero no todo es tan simple: este tiempo es máximo, es decir cambiar de mínimo a máximo y viceversa. Al intentar retirar el meandro, estos frentes llenos de basura son muy claramente visibles:

Si se emite una onda sinusoidal, la obstrucción de los frentes ya no es tan notable debido a la forma de onda. Sin embargo, si se aumenta la frecuencia, la señal sinusoidal se convierte en triangular y, con un aumento adicional, la amplitud de la señal disminuye.
Generación a 1 KHz ( 90% de amplitud ):
Generación a 10 KHz ( 90% de amplitud ):
Generación a 100 KHz ( 90% de amplitud ):
Los pasos ya son visibles, porque se cargan nuevos datos en el DAC a una frecuencia de 4 MHz.
Además, el borde posterior de la señal del diente de sierra está abarrotado y desde abajo la señal no alcanza el valor al que debería. Esto se debe a que la señal no tiene tiempo para alcanzar el nivel bajo especificado y el software está cargando nuevos valores
Generación a 200 KHz ( 90% de amplitud ):
Aquí ya puedes ver cómo todas las ondas se convirtieron en un triángulo.
Canales digitales
Con los canales digitales, todo es mucho más simple: en casi cualquier microcontrolador hay temporizadores que le permiten emitir una señal PWM a las salidas del microcontrolador. Es mejor usar un temporizador de 32 bits; en este caso, no necesita contar el temporizador previo, simplemente cargue el período en un registro y cargue el ciclo de trabajo requerido en otro registro.
Interfaz de usuario
Se decidió organizar la interfaz de usuario en cuatro rectángulos, cada uno con una imagen de la señal de salida, frecuencia y amplitud / ciclo de trabajo. Para el canal seleccionado actualmente, los datos de texto se muestran en blanco y el resto en gris.

Se decidió controlar los codificadores: el izquierdo es responsable de la frecuencia y el canal seleccionado actual (
cambia cuando se presiona el botón ), el derecho es responsable del ciclo de amplitud / trabajo y la forma de onda (
cambia cuando se presiona el botón ).
Además, se implementa el soporte para la pantalla táctil: cuando hace clic en un canal inactivo, se activa, cuando hace clic en un canal activo, la forma de onda cambia.
Por supuesto, DevCore está acostumbrado a hacer todo esto. El código para inicializar la interfaz de usuario y actualizar los datos en la pantalla se ve así:
Estructura que contiene todos los objetos de la IU Código de inicialización de interfaz de usuario Código de actualización de pantalla for(uint32_t i = 0U; i < CHANNEL_CNT; i++) { ch_dsc[i].img.SetImage(waveforms[ch_dsc[i].waveform]); snprintf(ch_dsc[i].freq_str_data, NumberOf(ch_dsc[i].freq_str_data), "Freq: %7lu Hz", ch_dsc[i].frequency); if(IsAnalogChannel(i)) snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Ampl: %7d %%", ch_dsc[i].duty); else snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Duty: %7d %%", ch_dsc[i].duty);
Se implementa una implementación interesante del clic del botón (
es un rectángulo sobre el que se dibujan los elementos restantes ). Si miró el código, debería haber notado algo así:
ch_dsc [i] .box.SetCallback (& Callback, this, nullptr, i); llamado en un bucle. Este es el trabajo de la función de devolución de llamada que se llamará cuando se presione el botón. Lo siguiente se transfiere a la función: la dirección de la función estática de la función estática de la clase, el puntero this y dos parámetros de usuario que se pasarán a la función de devolución de llamada: un puntero (
no utilizado en este caso, se pasa nullptr ) y un número (
se transmite el número de canal ).
Desde el banco universitario, recuerdo el postulado: "
Las funciones estáticas no tienen acceso a los miembros de la clase no estáticos ". Entonces esto
no es
cierto . Como una función estática es miembro de una clase,
tiene acceso a todos los miembros de la clase si tiene un enlace / puntero a esta clase. Ahora eche un vistazo a la función de devolución de llamada:
En la primera línea de esta función, se produce "
magia ", después de lo cual puede acceder a cualquier miembro de la clase, incluidos los privados.
Por cierto, esta función se llama en otra tarea (
renderizar la pantalla ), por lo que dentro de esta función debe ocuparse de la sincronización. En este simple proyecto de "un
par de noches ", no hice esto, porque en este caso particular no es esencial.
Código fuente del generador cargado en GitHub:
https://github.com/nickshl/WaveformGeneratorDevCore ahora se asigna a un repositorio separado y se incluye como un submódulo.
Bueno, ¿por qué necesito un generador de señal? Será en el próximo (
o uno de los siguientes ) artículos.