0% encontró este documento útil (0 votos)
27 vistas25 páginas

¿Cómo Funciona Un DSO?: Pero, Antes Que Nada, ¿Qué Es Un Osciloscopio?

Un osciloscopio digital muestra gráficamente el voltaje eléctrico en relación con el tiempo, permitiendo la visualización de señales eléctricas de alta velocidad. Funciona acondicionando la señal de entrada, convirtiendo voltajes en números mediante un ADC, y muestreando la señal a intervalos precisos para su análisis. Este documento detalla la construcción de un osciloscopio simple utilizando un microcontrolador STM32, con especificaciones sobre su funcionamiento interno y características técnicas.

Cargado por

dp500
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
27 vistas25 páginas

¿Cómo Funciona Un DSO?: Pero, Antes Que Nada, ¿Qué Es Un Osciloscopio?

Un osciloscopio digital muestra gráficamente el voltaje eléctrico en relación con el tiempo, permitiendo la visualización de señales eléctricas de alta velocidad. Funciona acondicionando la señal de entrada, convirtiendo voltajes en números mediante un ADC, y muestreando la señal a intervalos precisos para su análisis. Este documento detalla la construcción de un osciloscopio simple utilizando un microcontrolador STM32, con especificaciones sobre su funcionamiento interno y características técnicas.

Cargado por

dp500
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

¿Cómo funciona un DSO?

Pero, antes que nada, ¿qué es un osciloscopio?


Un osciloscopio, coloquialmente llamado osciloscopio, es un instrumento de medición
que muestra gráficamente el voltaje eléctrico en relación con el tiempo. Esto permite
al usuario visualizar y analizar señales eléctricas de alta velocidad que no se pueden
ver de otra manera.

En el pasado, los osciloscopios se construían utilizando tubos de rayos catódicos,


similares a los que se encuentran en los televisores antiguos. Funcionaban barriendo
un haz de electrones a través de una pantalla recubierta de fósforo, que era desviada
verticalmente por la señal de entrada. Esto haría que la pantalla brillara en cada
punto golpeado por el haz, lo que permitiría la visualización de la señal de entrada
como una forma de onda. Este proceso era puramente analógico y ocurría en tiempo
real. Como tal, todas las mediciones debían realizarse manualmente, midiendo
físicamente la forma de onda en la pantalla. Esto también significaba que las formas
de onda no se podían almacenar en ningún lugar (existían osciloscopios de
almacenamiento analógicos, pero eran muy poco comunes).

La mayoría de los osciloscopios de hoy en día son osciloscopios de almacenamiento


digital. Como su nombre indica, muestrean y almacenan la señal entrante de forma
digital. De este modo, la señal puede analizarse mediante un ordenador, lo que
permite realizar mediciones avanzadas y automáticas.

¿Cómo funciona un osciloscopio de almacenamiento digital?

Acondicionamiento de la señal

Antes de que pueda ser medida por los diversos componentes de un osciloscopio, la
señal de entrada debe estar condicionada de alguna manera. Por lo general, esto
implica escalarlo y atenuarlo mediante un componente llamado frontend analógico. El
elemento de medición real de nuestro instrumento solo puede muestrear voltajes en
el rango de 0 a 3,3 V. Con el fin de ampliar este rango a un dominio útil, atenuamos la
señal de entrada a la mitad y la centramos en torno a 1,65 V. Esto se hace mediante
el uso de un divisor de voltaje y un amplificador operacional para crear un voltaje de
referencia y otro divisor y amplificador operacional para atenuar y amortiguar la
señal. Esto permite que el instrumento mida voltajes que van desde -3,3 V hasta +3,3
V. El esquema de la interfaz se puede encontrar en el archivo [Link].

Conversión de voltaje en números


El voltaje eléctrico es una cantidad física que debe muestrearse y cuantificarse en un
valor numérico. Esto lo hace un ADC (un inversor de Analógico a Digital C). El ADC
utilizado en este proyecto está integrado en el microcontrolador y es un ADC de
registro de aproximación sucesiva. Esto significa que el ADC toma el voltaje de
entrada y lo compara con diferentes voltajes hasta que encuentra la coincidencia más
cercana. Dos de las principales características de un ADC son su resolución y su
máxima frecuencia de muestreo. La resolución nos dice cuántos bits usa para
representar números. Nuestro ADC es de 12 bits, lo que significa que hay
1
212 diferentes valores que puede generar. La frecuencia de muestreo máxima mide la
rapidez con la que el ADC puede medir la señal entrante. Nuestro ADC es capaz de
hasta 1,6 MSa/S (megamuestras por segundo, o millones de muestras por segundo).

Muestreo de una señal


Para reconstruir digitalmente una forma de onda, la señal debe muestrearse a
intervalos de tiempo precisos. Estamos logrando esto mediante el uso de uno de los
temporizadores de nuestro microcontrolador para generar la base de tiempo de
nuestro visor. De esta manera, el ADC captura una muestra en cada disparo del
temporizador. Los osciloscopios generalmente usan hardware dedicado para activar el
muestreo en segmentos precisos de la señal de entrada. En aras de la simplicidad,
nuestro osciloscopio simula esto en el software alineando la forma de onda mostrada
con la condición de disparo seleccionada.

Introducción de los datos en la memoria


Los osciloscopios digitales suelen tener una memoria de muestra dedicada y un
hardware que se encarga de la transferencia de datos desde el ADC sin que
intervenga el procesador principal. Estamos utilizando un enfoque similar aquí
también, haciendo uso del motor DMA (Direct Memory Access) del microcontrolador.
El procesador le dice al DMA que comience a extraer datos del ADC a un búfer en la
memoria y luego espera. Cada vez que se activa el ADC, se produce una transferencia
DMA. Una vez que el búfer está lleno, el DMA se detiene y le dice al procesador que
ha terminado de transferir datos. Con la señal muestreada en la memoria, ahora
podemos mostrar la forma de onda en la pantalla y realizar diferentes mediciones.

DIY Oscilloscope based on Stm32f103 Blue Pill and IPS LCD


ST7789 240x240

GitHub - VanAn-nd/DIY-Oscilloscope-base-STM32F103C8T6

Mode: Normal/X-Y mode

Channel: 2-CH

Sampling rate: 1MSa/s (STM32F303 get 10MSa/s)

Input impedance: 1M

Test signal: 1kHz / 3.3Vpp

Horizontal scale: 5us - 200ms (step by 1-2-5)

Vertical Sensitivity: 100mV - 10V (step by 1-2-5)

Waveform length: 1k - 2k (auto)

XY-Mode record length: 256pt - 2048pt (adjustable: step by 256++)

Mi propósito es hacer un osciloscopio simple para que todos puedan construirlo. Porque es difícil
obtener un amplificador operacional de gran ancho de banda en mi ubicación. Por supuesto,
puede usar uno mayor si lo tiene: AD8001, AD8065, AD8061, OPA2354, ... El resultado es bueno o
no dependiendo de su interfaz analógica...

2
3
Microcontrolador: STM32F030F4
Mini Oscilloscope : 9 Steps (with Pictures) - Instructables

El código ya está disponible en GitLab: [Link]

La más pequeña que tengo es la STM32F030F4, que en Ebay suele venderse como
placa de
desarrollo. Dentro de este microcontrolador hay un ADC decente, puede hacer una
conversión en 1us. Los resultados de las conversiones se pueden leer desde un
registro, pero, y eso es muy importante aquí, también se transfieren a través de DMA
a cualquier lugar de la memoria haciendo que el proceso sea muy rápido y sin cargar
la cpu. En este microcontrolador se encuentran los temporizadores habituales,
dispone de SPI e I2C, un RTC, un USART y 2 Watchdogs. Por supuesto, no puede usar
todos los periféricos al mismo tiempo, ya que esta versión del STM32F030 tiene solo
20 pines y algunos de ellos se usan para cosas como energía, reinicio, cristal,
programación y selección de arranque. Para programar los microcontroladores
fabricados por ST Microelectronics se necesita una herramienta llamada STLink-V2,
disponible a través de Mouser, Farnell y otros, un clon barato de la misma está
disponible en Ebay y otros. La placa STM32F030F4 se vende por menos de 3 euros y
la STLink-V2 por un precio similar. Si tiene una placa de desarrollo oficial hecha por ST
Microelectronics, como una "placa Nucleo", hay un STLink-V2 en ella que se puede
quitar de ella. Eso es lo que hice y lo puse en una pequeña caja de plástico.

Especificaciones

La pantalla TFT tiene una resolución de 160 x 128 píxeles, lo que significa que el eje X
(base de tiempo) necesita 160 muestras ADC. El ADC necesita un mínimo de 1 us por
muestra y creo que 10 ondas de una señal de una pantalla tan pequeña está en el
límite de lo útil. Esto significa 16 píxeles por forma de onda. Como el ADC necesita
alrededor de 1us por muestra, eso es 16us, esa es una forma de onda con una
frecuencia de 62500 Hz. Eso no es mucho cuando estás acostumbrado a un
osciloscopio digital real de 100 MHz, pero es suficiente para audio, proyectos de LED y
más experimentos de baja frecuencia.
Al final ni siquiera pude conseguir que el ADC hiciera 1.000.000 de conversiones por
segundo, así que me conformé con 800.000. Por lo tanto, la base de tiempo sube a
40us/división. Con 5 divisiones en la pantalla, significa 200 us por pantalla completa.
Una forma de onda de 5 kHz llena toda la pantalla y creo que el límite de utilidad es
4
de alrededor de 50 kHz. En el extremo inferior, decidí que 200 ms/división (1
segundo/pantalla) era un buen momento, por lo que una señal de 1 Hz utiliza todo el
ancho de la pantalla.

El ADC convierte el voltaje en su entrada referenciado a 3.3V (Vdd). Puedes elegir las
resoluciones de 12, 10, 8 y 6 bits y cuanto más bajo vayas, más rápidas serán las
conversiones. Como la pantalla TFT tiene un eje Y (resolución vertical) de 128 píxeles
(7 bits), no es necesario que el ADC supere los 7 bits, pero no hay una resolución de 7
bits disponible, por lo que se establece en 8 bits. El bit menos significativo se
descartará con un desplazamiento a la derecha de 1 bit.

Se requiere atenuación para poder medir un poco más de 3.3V que el ADC es capaz
de hacer, amplificación si desea medir señales más pequeñas que eso, y sería bueno
poder medir un voltaje negativo también. Esto significa tener una fuente de
alimentación positiva y negativa o tener un desplazamiento agregado al voltaje de
entrada. Debido a que quería usar solo una batería y mantener el dispositivo lo más
simple posible, elegí el método de compensación. Esto significa que el terminal de
tierra de la entrada no está conectado a tierra en absoluto, está conectado al voltaje
de compensación, a esto lo llamo tierra virtual. También significa que tiene que usar
este osciloscopio alimentado por baterías.

El osciloscopio tiene una sola sensibilidad: 1 voltio / división. Sí pensé en añadir un


amplificador y un atenuador con pequeños relés o un potenciómetro digital. Pero el
relé que tengo necesita al menos 5V para funcionar y el potenciómetro digital era
demasiado ruidoso para ser útil. Por supuesto, puedes añadirle un amplificador y/o un
atenuador, sólo tienes que ponerlo delante de la entrada. A continuación, el
osciloscopio no debe suponer una gran carga para el circuito que se está midiendo. La
mayoría de los osciloscopios usan 1 Mohm, y este también. Puede usar una sonda real
de 10x con él, que aumentará tanto el rango de voltaje como la impedancia de
entrada diez veces, pero también es posible que deba agregar compensación de
frecuencia con un condensador variable. No lo he probado.

El ancho de banda analógico de un osciloscopio debe ser lo más alto posible, como
regla general, al menos 10 veces la frecuencia que desea medir. Después de todo,
una onda cuadrada de solo 10 kHz tiene muchos armónicos y necesita tener al menos
un ancho de banda de 90 kHz para ver el noveno armónico. Dicho de otra manera, si
intentas ver una onda cuadrada de 10MHz en un osciloscopio de 10 MHz, verás muy
poca onda cuadrada, pero sobre todo una onda sinusoidal de 10 MHz. Aquí un ancho
de banda de 500kHz sería suficiente, pero más es mejor. Utilicé un amplificador
operacional con un producto de ganancia/ancho de banda de 10MHz un Microchip
MCP6021, por la sencilla razón de que tengo algunos en stock. Si desea usar otro,
recuerde que debe funcionar a 3.3V.

Con un desplazamiento de 0 V a 3 V, el voltaje aceptable en la entrada va de -6 V a


+6 V. ¡Pero siempre con un valor máximo de 6V! Entonces, ya sea de -6 V a 0 V,
pasando por -3 V a +3 V, hasta 0 V a + 6 V. Lo suficientemente bueno para la mayoría
de los proyectos de Arduino que pueda tener, y si es necesario, duplicar la resistencia
de entrada de 1Mohm a 2Mohm también duplicará el rango de voltaje.

El nivel de disparo se puede configurar en cualquier lugar de la pantalla, lo que


significa que se puede configurar desde casi -6 V hasta casi +6 V. Pero en realidad no
tiene nada que ver con ningún voltaje, solo con la posición de la traza en la pantalla.
5
Además, solo actúa sobre los bordes ascendentes de una señal. Si lo desea, puede
agregar la opción de disparar en los bordes descendentes también, agregarlo a la
fuente, no pensé que fuera necesario.

La última especificación que quiero es que la batería de iones de litio se pueda cargar
a través de una pequeña placa con un conector mini-usb. Al principio quería usar una
batería de iones de litio de tamaño 18650 pero era demasiado grande, ahora se usa
una pequeña batería rectangular, tiene las mismas dimensiones que la batería de un
teléfono (tonto) Samsung y otros utilizados en sus teléfonos, pero es el doble de
gruesa. Simplemente use cualquier batería de iones de litio que tenga y que le quede.
Siempre que el voltaje nominal sea de 3,7 voltios, está bien.

En resumen, estas son las especificaciones


 Resistencia de entrada: 1Mohm
 Sensibilidad de 1Mohm
 Base de tiempo de 1V/div 200ms/div... 40us/div
 voltaje de entrada -6V... +6V
 Nivel de disparo Cualquier nivel en la pantalla
 Alimentado por pilas
Funcionamiento interno
Biblioteca
TFT: En primer lugar, necesitaba que la pantalla funcionara. Afortunadamente para la
mayoría de las pantallas estándar hay bibliotecas listas para usar, pero no pude
encontrar una para esta pantalla y un microcontrolador STM32. El que porté a STM32
estaba hecho para Arduino (¿hecho por Sparkfun? ¿Adafruit? ¿Alguien más?). No
recuerdo y no guardé la fuente original o los nombres de los creadores originales
cuando lo porté a STM32, lo siento.

- Así que todos los honores de la biblioteca ST7735 son para los fabricantes, sean
quienes sean -

Después de portarlo a STM32, descubrí que la pantalla no es muy rápida.


Afortunadamente, usa SPI y puede hacer que SPI vaya bastante rápido en este
microcontrolador. Pero aún así, necesitaba enviarle la menor cantidad de datos
posible para mantener la frecuencia de actualización de la pantalla razonable. No lo
he medido, pero creo que, en la configuración de base de tiempo más corta, es de
alrededor de 15 a 20 Hz. En la configuración de base de tiempo más larga (200 ms/div
- 1 segundo/pantalla) notarás que la frecuencia de actualización no es de 1 Hz, ¡sino
de solo 0,5 Hz! Lo explicaré más adelante.

Los codificadores rotativos muy, muy malos En segundo lugar, hay que leer el
codificador rotatorio. Esto me causó la mayoría de los problemas, no es que no
supiera cómo hacerlo, es solo una simple señal de cuadratura. No, resultó que los 10
codificadores que compré (Ebay) eran de tan mala calidad que no solo necesité hacer
la eliminación de rebotes de software, sino también la eliminación de rebotes en el
hardware. Y aun así, no puedo girar la perilla demasiado rápido ya que perderá
pulsos. Normalmente hago solo derebotes de software, ya que no necesita nada
extra, ni resistencias pullup, ni condensadores, pero en este caso eran muy
necesarios.

6
ADC y DMA Lo más importante es la entrada de datos analógicos. Como dije, el ADC
hace una conversión de 8 bits en aproximadamente 1us. Se inicia mediante un
temporizador (TIM3) que funciona continuamente y envía pulsos al ADC de acuerdo
con la configuración de la base de tiempo actual. La velocidad más baja (200 ms/div --
> 1 segundo/pantalla) es de 160 Hz. Por lo tanto, el ADC realiza 160 conversiones por
segundo, llenando la pantalla de 160 píxeles en 1 segundo. La velocidad más alta es
de 800 kHz, por lo que la pantalla se llena en 160*(1/800.000) = 200us. ¡Ojalá la
pantalla y el software fueran tan rápidos! Entonces podrías tener una frecuencia de
actualización de 5 kHz. (cualquier visor analógico antiguo hace eso sin sudar)

En su lugar, los datos del ADC se transfieren a una matriz: adc_buffer[] en la memoria.
Esto se hace con DMA, lo que significa que la CPU del microcontrolador no es
necesaria para hacer eso, puede continuar con lo que sea que esté haciendo. Esto
hace que el almacenamiento de los datos sea muy sencillo y rápido. Cuando el DMA
está listo con el número programado de valores que necesita transportar, establece el
indicador TC (transmisión completa) y desencadena una interrupción. La interrupción
en sí misma no hace mucho, solo borra la bandera TC y establece una variable
llamada "token" como una señal para la rutina principal que le dice que hay datos
listos para ser mostrados. El ADC continúa con las conversiones y el DMA sigue
transportando esos resultados al adc_buffer[]. Por lo tanto, no importa lo que esté
sucediendo dentro del microcontrolador, hay un flujo interminable de valores que
entran en adc_buffer.

Como se dijo, la pantalla tiene 160 píxeles de ancho, por lo que solo se necesitan 160
valores para mostrar una forma de onda completa. El adc_buffer[] contiene en
realidad 320 muestras. Por lo tanto, el DMA almacena 320 valores en él antes de
desencadenar una interrupción TC. Esto se hace porque la activación se realiza en el
software. Y como es muy poco probable que el primer valor en el adc_buffer[] sea el
lugar donde debería estar el desencadenamiento. Tenemos que encontrar el lugar
donde está ese punto. Por lo tanto, se leen 320 valores y en los primeros 160 de ellos
se busca el punto de activación real.

Lo que se hace es que en el adc_buffer[] se encuentra el punto de activación


comprobando si el valor está en el valor de activación en si el siguiente valor está
justo por encima de él. Esto funciona bastante bien, pero necesita un búfer más
grande que el tamaño real de la pantalla. Intenté con el doble de tamaño y el
cuádruple de tamaño, pero eso hizo muy poca diferencia, así que me quedé con 320.

Esta también es la razón por la que la frecuencia de actualización en la configuración


de base de tiempo más baja es más lenta de lo que cabría esperar. Como mencioné
antes, cuando usa la configuración de 200 ms / div, una pantalla llena de datos tarda
1 segundo, pero debido a que se realiza el doble de conversiones, toma 2 segundos.
En la configuración de la base de tiempo más rápida, no lo notará tanto.

El código
MAIN.C
debido a que las conversiones nunca se detienen, los valores en adc_buffer[] se
sobrescribirán continuamente, en la configuración de base de tiempo más rápida, esto
sucederá con todos los valores cada 400us. Para evitar mostrar datos incorrectos, lo
primero que hace la rutina principal es hacer una copia de adc_buffer[] en
display_buffer[]. Un poco más tarde también copia los mismos datos en

7
erase_buffer[]. Esto se hace porque borrar toda la pantalla lleva "una eternidad". Lo
que se hace ahora es: la forma de onda anterior (verde) se sobrescribe exactamente
con la misma forma de onda en negro en la siguiente ronda, justo antes de que se
muestre la nueva.

El resto del programa es más o menos cosmético, se muestra una cuadrícula de líneas
horizontales y verticales, los valores actuales de sensibilidad y base de tiempo. La
línea de cero voltios se muestra más brillante que todas las demás y se mueve con el
desplazamiento. Se muestra una pequeña línea donde está el nivel de disparo actual.
Lo que en este momento está activo en el codificador rotativo, la base de tiempo, el
desplazamiento o el nivel de activación, se muestra en amarillo.

SystemClock_Config Mientras experimentaba para obtener la mayor velocidad del


microcontrolador, aumenté el reloj muy por encima del máximo oficial de 48 MHz, a
64 MHz e incluso a 80 MHz. Todo funcionó sin problemas incluso en 80MHz, pero no
quería mantenerlo tan alto, así que lo reduje a 64MHz. Esto todavía está muy lejos de
las especificaciones de ST Microelectronics, por lo que el microcontrolador *podría*
funcionar mal. Dicho esto, dudo que cause problemas porque esas especificaciones
son las que ST Microelectronics dice que definitivamente funcionarán en todas las
condiciones, de 2.4V a 3.6V y de -40C a +105C. Aquí se encuentra en mi escritorio a
25 ° C y con un 3.3 V estable, no creo que lo use mucho a -40 Celsius.

MX_ADC_Init En realidad, queda un pin en el microcontrolador, me aseguré de que


fuera PA1, ya que ese pin podría usarse como una segunda entrada ADC. Podría ser
posible convertir esto en un osciloscopio de dos canales. Pero no lo he probado
(todavía). Por el momento, PA1 está configurado como una salida que utilizo para un
debug-led. De notar es la configuración del modo circular del DMA al ADC, por lo que
nunca se detiene. El reloj ADC es el reloj del sistema dividido por 4, esto es de 16MHz
y que también está por encima de las especificaciones de ST Microelectronis. No debe
ser superior a 14 MHz. De nuevo, esto funciona. Cada conversión se inicia en el flanco
ascendente de una señal TRGO de TIM3 (ver más abajo) El ADC no produce ninguna
interrupción, de eso se encarga el DMA después de 320 muestras.

MX_SPI1_Init No hay mucho que contar, excepto que funciona a 16MHz, lo cual es
bueno con la pantalla TFT ST7735.

MX_TIM3_Init Este temporizador es responsable del inicio de las conversiones de ADC.


Funciona a 64MHz y en cada UPDATE, (desbordamiento) envía un pulso al ADC a
través de la salida TRGO. Al cambiar el valor máximo del temporizador en el registro
llamado AutoReloadRegister (ARR), se cambia la frecuencia de los pulsos TRGO (la
base de tiempo). Este cambio se realiza en la rutina de interrupción de TIM6 (ver más
abajo)

MX_DMA_Init Just habilita sus interrupciones en el NVIC.

MX_TIM14_Init Este produce una onda cuadrada en PB1 con una frecuencia de un
poco más de 100 kHz y un ancho de pulso variable. Un filtro de paso bajo hecho con
una resistencia de 1k y un condensador de 10uF transforma esto en el voltaje de
compensación para el amplificador operacional.

MX_TIM16_Init ¿El rebote del codificador rotatorio y disminuye el valor de tiempo de


espera? Este temporizador normalmente está apagado y solo se inicia cuando se
8
detecta movimiento en el codificador rotatorio. Esta detección de movimiento se
realiza a través de interrupciones EXTI en el GPIO conectado al codificador rotatorio.
Cuando se ejecuta, este temporizador produce 1000 interrupciones por segundo.
Cuando el tiempo de espera llega a cero, esta rutina de interrupción se deshabilita a
sí misma y vuelve a habilitar las interrupciones EXTI.

MX_GPIO_Init Establece varios GPIO como entrada y salida y se encarga de habilitar el


EXTI en algunas entradas.

STM32F0XX_IT.C

DMA1_Channel1_IRQHandler Borra su marca TC y luego establece el token en 1. Esto


indica para main.c que hay nuevos datos que deben mostrarse.

TIM16_IRQHandler Determina si se presiona el botón del codificador giratorio y si el


codificador giratorio se gira en el sentido de las agujas del reloj o en el sentido
contrario a las agujas del reloj. Después de eso, establece la configuración de la base
de tiempo para TIM3, la configuración de compensación en TIM14 y el nivel de
disparo. También establece cosas como el color en el que se deben mostrar las
opciones. Lo último que hace esta rutina de interrupción es deshabilitarse después de
un corto tiempo (tiempo de espera) y volver a habilitar las interrupciones para el EXTI
(ver más abajo).

EXTI2_3_IRQHandler Interrupción para el botón del codificador giratorio. Habilita


TIM16 para el deboting y se deshabilita a sí mismo. Establece el tiempo de espera en
20 ms

EXTI4_15_IRQHandler Interrupción de la conexión del codificador rotatorio A y B.


Habilita TIM16 para el deboting y se deshabilita a sí mismo. Establece el tiempo de
espera en 20 ms

9
10
Osciloscopio STM32 con exportación FFT y SD
[Link] - STM32 Oscilloscope with FFT and SD export - The Arduino code

Este no es solo otro osciloscopio STM32, es mío. Como tal, fue diseñado a mi gusto.
Exprimí la tasa de conversión máxima (2,57 Msps) de un solo ADC, utilicé el
controlador DMA incorporado para maximizar la velocidad de transferencia de datos y
lo hice exportar los datos adquiridos y calculados en una tarjeta SD. Fue construido
sobre una placa de propósito general y tiene un circuito de entrada flexible.

Osc
iloscopio STM32 - Señal cuadrada de 500 kHz, ciclo de trabajo del 50%. Izquierda:
forma de onda. Derecha: el espectro de potencia FFT.

Esta es una continuación de mi artículo anterior sobre la construcción del osciloscopio


STM32. Hasta ahora, este proyecto implicó el diseño de esquemas, la fabricación de
PCB, la soldadura de piezas TH/SMD, la programación C/C++ de bajo nivel, algún
contacto con el ensamblador de STM32, el sistema de archivos FAT32, la gestión de la
memoria y mucha diversión con el diseño de la interfaz.

Características técnicas:
Un canal de entrada
ADC de 2,57 Msps, que acepta frecuencias de señal de hasta 1,28 MHz
Calcula los valores
mínimos, máximos y medios
Análisis de espectro FFT
Detección de frecuencia fundamental
Exportación con tarjeta SD de la forma y el espectro
de la onda de señal
Función de congelación
Selección de la frecuencia de muestreo

11
Os
ciloscopio STM32 - Señal cuadrada de 1 MHz, 50% de ciclo de trabajo. Izquierda:
forma de onda. Derecha: el espectro de potencia FFT.

El STM32F103C8 puede desplegar la mitad de su velocidad de reloj para el muestreo


ADC (configurando el preescalador en 2, véase RCC_ADCPRE_PCLK_DIV_2) y
calcula FFT para 1024 muestras en 2 ms. El condensador de muestreo puede cargarse
en 1,5 ciclos ADC utilizando la configuración de ADC_SMPR_1_5 y la cuantificación de
la muestra tarda otros 12,5 ciclos ADC en completarse. Eso significa una frecuencia
de adquisición de

2.571 MHz (= 72 MHz / 2 / (1.5 + 12.5)).

Para alcanzar niveles razonables de SNR a esta velocidad de muestreo, se debe


satisfacer la constante de tiempo RC para el condensador de muestreo. La buena
noticia es que el C ADC es pequeño, 8pF. Lo malo es que hay una resistencia en serie
RADC de 1 kOhm (clasificación máxima) justo antes del condensador de muestreo.
Ahora, de acuerdo con las especificaciones del MCU, la resistencia de entrada
RAIN debe obedecer lo siguiente:

donde TS = 1,5 ciclos ADC, fADC = 36 MHz, CADC = 8 pF, RADC <= 1 kOhm y N es el
número efectivo de bits que almacenan datos sin errores.

El resultado no es alentador, ya que solo se pueden utilizar los 5 bits más


significativos de los datos convertidos. Las únicas opciones factibles para producir una
resolución de 12 bits serían reducir la f ADC a 18 MHz o utilizando un tiempo de
muestreo más alto (el siguiente es de 7,5 ciclos ADC).

Experimentos

han indicado que una señal de 250 kHz muestreada a 2,57 Msps, 1,8 Msps, 1,38 Msps
o incluso 529 ksps proporciona mediciones de amplitud similares, lo que lleva a creer
12
que RADC de hecho, es mucho más bajo que (tal vez la mitad de) el máximo
mencionado en las especificaciones y que el muestreo de señales a 2,57 MHz no
planteará problemas significativos.

Osciloscopio STM32 - señal cuadrada de 250 kHz, ciclo de trabajo del 50%. Se observó
un error de 20 mV a diferentes velocidades de muestreo. Izquierda: a 2,57 MSPS.
Derecha: a 529 kSPS.

Es una buena noticia teniendo en cuenta que la etapa de entrada es totalmente


pasiva: compuesta por un divisor de voltaje: la corriente consumida por la entrada de
la MCU hace que R3 actúan como un limitador de corriente: en el lado del generador,
una resistencia limitadora de corriente y un condensador de filtrado de paso alto en
serie con la entrada ADC. No hace falta decir que la entrada debe contener al menos
un seguidor de voltaje.

Osciloscopio STM32 - Circuito de etapa de entrada simple. La señal de prueba es


generada por un AtMega328p alimentado a 3.3V.

El código

todavía usa la estructura y las bibliotecas de Arduino, excepto para las rutinas ADC,
DMA y FFT relacionadas con STM32. Se supone que un TFT NT35702 está enganchado
como se describe aquí, los pines de la tarjeta SD van al puerto SPI2 del STM32 y los
botones y entradas están conectados como se indica en la publicación anterior.

13
También se debe instalar la biblioteca SdFat para que el código de la tarjeta SD
funcione y agregar (arrastrando y soltando) los archivos de ensamblador
relacionados con el cr4_fft_1024_stm32 al proyecto IDE de Arduino.

La máquina de estado itera a través de los estados de sondeo de claves, adquisición,


FFT y visualización. Sí, renuncié a la administración de claves basada en
interrupciones en favor del sondeo de claves y eso se debe a que la máquina ya no
espera en otros estados. Las 1024 muestras se adquieren y almacenan directamente
en la memoria mediante la funcionalidad DMA integrada. A continuación, las muestras
se mastican y se transforman en bins de frecuencia mediante la implementación FFT
del ensamblador. La pantalla utiliza una escala lineal para mostrar el espectro de la
señal, mientras que la potencia de la fundamental se expresa en dB.
Para obtener la versión actualizada, consulte el repositorio de GitHub.
/*
* STM32 Digital Oscilloscope
* using the STM32F103C8 MCU and the NT35702 2.4 inch TFT display
* [Link]
*
* [Link]
* 2016-2018
*/
#include <Adafruit_ILI9341_8bit_STM.h>
#include <Adafruit_GFX.h>
#include <SPI.h>
#include "SdFat.h"

#include <table_fft.h>
#include <cr4_fft_stm32.h>

static const uint8_t SD_CHIP_SELECT = PB12;


static const uint8_t TIME_BUTTON = PA15;
static const uint8_t TRIGGER_BUTTON = PB10;
static const uint8_t FREEZE_BUTTON = PB11;
static const uint8_t TEST_SIGNAL = PA8;
static const uint8_t CHANNEL_1 = PB0;
static const uint8_t CHANNEL_2 = PB1;

static const uint16_t BLACK = 0x0000;


static const uint16_t BLUE = 0x001F;
static const uint16_t RED = 0xF800;
static const uint16_t GREEN = 0x07E0;
static const uint16_t CYAN = 0x07FF;
static const uint16_t MAGENTA = 0xF81F;
static const uint16_t YELLOW = 0xFFE0;
static const uint16_t WHITE = 0xFFFF;

static const uint16_t BACKGROUND_COLOR = BLUE;


static const uint16_t DIV_LINE_COLOR = GREEN;
static const uint16_t CH1_SIGNAL_COLOR = YELLOW;

static const uint16_t ADC_RESOLUTION = 4096; // units


static const uint16_t EFFECTIVE_VERTICAL_RESOLUTION = 200; // pixels
static const uint16_t SCREEN_HORIZONTAL_RESOLUTION = 320; // pixels
static const uint16_t SCREEN_VERTICAL_RESOLUTION = 240; // pixels
static const uint16_t DIVISION_SIZE = 40; // pixels
static const uint16_t SUBDIVISION_SIZE = 8; // pixels (DIVISION_SIZE / 5)
static const uint16_t BUFFER_SIZE = 1024; // bytes
static const uint8_t TRIGGER_THRESOLD = 127; // units
static const float ADC_SCREEN_FACTOR = (float)EFFECTIVE_VERTICAL_RESOLUTION /
(float)ADC_RESOLUTION;

14
static const float VCC_3_3 = 3.3; // volts

const uint8_t DT_DT[] = {4, 2, 1, 1, 1, 1, 1, 1, 1,


1, 1};
const uint8_t DT_PRE[] = {0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1};
const uint8_t DT_SMPR[] = {0, 0, 0, 1, 2, 3, 4, 5, 6,
7, 7};
const float DT_FS[] = {2571, 2571, 2571, 1800, 1384, 878, 667, 529, 429,
143, 71.4};
const float DT_DIV[] = {3.9, 7.81, 15.63, 22.73, 29.41, 45.45, 55.55, 83.33, 95.24,
293.3, 586.6};

Adafruit_ILI9341_8bit_STM tft;
SdFat sd(2);
SdFile file;

uint8_t bk[SCREEN_HORIZONTAL_RESOLUTION];
uint16_t data16[BUFFER_SIZE];
uint32_t data32[BUFFER_SIZE];
uint32_t y[BUFFER_SIZE];
uint8_t time_base = 7;
uint16_t i, j;
uint8_t state = 0;
uint16_t maxy, avgy, miny;

volatile uint8_t h = 1, h2 = -1;


volatile uint8_t trigger = 1, freeze = 0;
volatile bool bPress[3], bTitleChange = true, bScreenChange = true;
volatile static bool dma1_ch1_Active;

bool wasPressed(int pin, int index) {


//
if (HIGH == digitalRead(pin)) {
// isn't pressed
if (bPress[index]) {
// but was before
bPress[index] = false;
}
return false;
}
// is pressed
if (!bPress[index]) {
// and wasn't before
bPress[index] = true;
return true;
}
// but was before
return false;
}

// ------------------------------------------------------------------------------------
// The following section was inspired by [Link]

void setADCs() {
//
switch (DT_PRE[time_base]) {
//
case 0: rcc_set_prescaler(RCC_PRESCALER_ADC, RCC_ADCPRE_PCLK_DIV_2); break;
case 1: rcc_set_prescaler(RCC_PRESCALER_ADC, RCC_ADCPRE_PCLK_DIV_4); break;
case 2: rcc_set_prescaler(RCC_PRESCALER_ADC, RCC_ADCPRE_PCLK_DIV_6); break;
case 3: rcc_set_prescaler(RCC_PRESCALER_ADC, RCC_ADCPRE_PCLK_DIV_8); break;
default: rcc_set_prescaler(RCC_PRESCALER_ADC, RCC_ADCPRE_PCLK_DIV_8);
}
switch (DT_SMPR[time_base]) {
15
//
case 0: adc_set_sample_rate(ADC1, ADC_SMPR_1_5); break;
case 1: adc_set_sample_rate(ADC1, ADC_SMPR_7_5); break;
case 2: adc_set_sample_rate(ADC1, ADC_SMPR_13_5); break;
case 3: adc_set_sample_rate(ADC1, ADC_SMPR_28_5); break;
case 4: adc_set_sample_rate(ADC1, ADC_SMPR_41_5); break;
case 5: adc_set_sample_rate(ADC1, ADC_SMPR_55_5); break;
case 6: adc_set_sample_rate(ADC1, ADC_SMPR_71_5); break;
case 7: adc_set_sample_rate(ADC1, ADC_SMPR_239_5); break;
default: adc_set_sample_rate(ADC1, ADC_SMPR_239_5);
}
adc_set_reg_seqlen(ADC1, 1);
ADC1->regs->SQR3 = PIN_MAP[CHANNEL_1].adc_channel;
ADC1->regs->CR2 |= ADC_CR2_CONT; // | ADC_CR2_DMA; // Set continuous mode and DMA
ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}

void real_to_complex(uint16_t * in, uint32_t * out, int len) {


//
for (int i = 0; i < len; i++) out[i] = in[i];// * 8;
}

uint16_t asqrt(uint32_t x) { //good enough precision, 10x faster than regular sqrt
//
int32_t op, res, one;
op = x;
res = 0;
one = 1 << 30;
while (one > op) one >>= 2;
while (one != 0) {
if (op >= res + one) {
op = op - (res + one);
res = res + 2 * one;
}
res /= 2;
one /= 4;
}
return (uint16_t) (res);
}

void inplace_magnitude(uint32_t * target, uint16_t len) {


//
uint16_t * p16;
for (int i = 0; i < len; i ++) {
//
int16_t real = target[i] & 0xFFFF;
int16_t imag = target[i] >> 16;
// target[i] = 10 * log10(real*real + imag*imag);
uint32_t magnitude = asqrt(real*real + imag*imag);
target[i] = magnitude;
}
}

uint32_t perform_fft(uint32_t * indata, uint32_t * outdata, const int len) {


//
cr4_fft_1024_stm32(outdata, indata, len);
inplace_magnitude(outdata, len);
}

static void DMA1_CH1_Event() {


//
dma1_ch1_Active = 0;
}

void adc_dma_enable(const adc_dev * dev) {


16
//
bb_peri_set_bit(&dev->regs->CR2, ADC_CR2_DMA_BIT, 1);
}
// ------------------------------------------------------------------------------------

void export_to_sd() {
//
[Link](170, 20);
[Link](WHITE);
[Link](1);
[Link]("Writing to SD ...");
[Link](170, 20);
if (![Link](SD_CHIP_SELECT, SD_SCK_HZ(F_CPU/4))) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("No SD card detected");
return;
}
delay(500);
if (![Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("File system init failed.");
return;
}
uint8_t index;
if (![Link]("DSO")) {
// no pre-exising folder structure
if (![Link]("DSO")) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't create folder");
return;
}
}
if (![Link]("DSO/[Link]")) {
// no index file
index = 1;
if (![Link]("DSO/[Link]", O_CREAT | O_WRITE)) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't create idx file");
return;
}
[Link](index);
if (![Link]() || [Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Idx file write error");
return;
}
[Link]();
} else {
//
if (![Link]("DSO/[Link]", O_READ)) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't open idx file");
return;
}
if (![Link](&index, 1)) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't read idx file");
return;
17
}
if (![Link]() || [Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("File write error");
return;
}
[Link]();
}
String s = "DSO/Exp";
s += index;
s += ".dat";
if (![Link](s.c_str(), O_CREAT | O_WRITE | O_EXCL)) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't data create file");
return;
}
[Link]("Time series");
for (uint16_t i = 0; i < BUFFER_SIZE; i ++) {
//
[Link](data16[i], DEC);[Link](", ");
}
[Link](" ");
[Link]("Fs: ");[Link](DT_FS[time_base]);[Link]("kHz");
[Link]("Spectrum");
for (uint16_t i = 0; i < BUFFER_SIZE/2; i ++) {
//
[Link](y[i], DEC);[Link](", ");
}
[Link](" ");
if (![Link]() || [Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("File write error");
return;
}
[Link]();
s += ".img";

if (![Link](s.c_str(), O_CREAT | O_WRITE | O_EXCL)) {


//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't image create file");
return;
}
[Link]("IMX");
for (uint16_t i = 0; i < BUFFER_SIZE; i ++) {
//
[Link](data16[i], DEC);[Link](", ");
}
[Link](" ");
[Link]("Fs: ");[Link](DT_FS[time_base]);[Link]("kHz");
[Link]("Spectrum");
for (uint16_t i = 0; i < BUFFER_SIZE/2; i ++) {
//
[Link](y[i], DEC);[Link](", ");
}
[Link](" ");
if (![Link]() || [Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("File write error");
return;
}
18
[Link]();

index ++;
if (![Link]("DSO/[Link]", O_CREAT | O_WRITE)) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Can't create idx file");
return;
}
[Link](index);
if (![Link]() || [Link]()) {
//
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("Idx file write error");
return;
}
[Link]();
[Link](169, 19, 150, 9, BACKGROUND_COLOR);
[Link]("File write success");
delay(2000);
[Link](170, 19, 150, 9, BACKGROUND_COLOR);
}

void setup() {
//
[Link]();
[Link](3);

bPress[0] = false;
bPress[1] = false;
bPress[2] = false;

adc_calibrate(ADC1);
}

void loop() {
//
if (state == 0) {
//
[Link](BACKGROUND_COLOR);
[Link](15, 100);
[Link](YELLOW);
[Link](3);
[Link]("[Link]");
// analogWrite(TEST_SIGNAL, 127);
delay(1500);
[Link](BACKGROUND_COLOR);
state = 1;
}
if (state == 1) {
// init
state = 2;
}
if (state == 2) {
// buttons check
if (wasPressed(TIME_BUTTON, 0)) {
// toggling the time division modes
time_base ++;
if (trigger == 0) {
// spectrum
if (time_base <= 2) time_base = 3;
}
time_base = time_base % sizeof(DT_DT);
h = DT_DT[time_base];
bScreenChange = true;
19
}
if (wasPressed(TRIGGER_BUTTON, 1)) {
// toggling the trigger mode
trigger ++;
trigger = trigger % 4;
bScreenChange = true;
bTitleChange = true;
}
if (wasPressed(FREEZE_BUTTON, 2)) {
// toggling the freeze screen
freeze = (freeze > 0) ? 0 : 3;
bTitleChange = true;
}
if (freeze) {
// frozen screen
state = 5;
} else {
// live screen
state = 3;
}
}
if (state == 3) {
// acquisition

setADCs();
dma_init(DMA1);
dma_attach_interrupt(DMA1, DMA_CH1, DMA1_CH1_Event);
adc_dma_enable(ADC1);
dma_setup_transfer(DMA1, DMA_CH1, &ADC1->regs->DR, DMA_SIZE_16BITS, data16,
DMA_SIZE_16BITS, (DMA_MINC_MODE | DMA_TRNS_CMPLT));
dma_set_num_transfers(DMA1, DMA_CH1, BUFFER_SIZE);
dma1_ch1_Active = 1;
dma_enable(DMA1, DMA_CH1); // enable the DMA channel and start
the transfer

while (dma1_ch1_Active) {}; // waiting for the DMA to complete


dma_disable(DMA1, DMA_CH1); // end of DMA trasfer

real_to_complex(data16, data32, BUFFER_SIZE); // data format conversion


perform_fft(data32, y, BUFFER_SIZE); // FFT computation

state = 4;
}
if (state == 4) {
// display signal screen
if (bScreenChange) {
// massive change on screen
bScreenChange = false;
[Link](BACKGROUND_COLOR);
bTitleChange = true;
} else {
// clear previous wave
if (trigger == 0) {
// clear previous spectrum
for (i = 1; i < SCREEN_HORIZONTAL_RESOLUTION; i ++) {
//
[Link](
i,
bk[i],
i + 1,
bk[i + 1],
BACKGROUND_COLOR);
}
} else {
// clear previous time samples
20
for (i = 0, j = 0; j < SCREEN_HORIZONTAL_RESOLUTION; i ++, j += h2) {
//
[Link](
j,
bk[i],
j + h2,
bk[i + 1],
BACKGROUND_COLOR);
}
}

}
// re-draw the divisions
for (i = 0; i < SCREEN_HORIZONTAL_RESOLUTION; i += DIVISION_SIZE) {
//
for (j = SCREEN_VERTICAL_RESOLUTION; j > 13; j -= ((i == 160) ?
SUBDIVISION_SIZE : DIVISION_SIZE)) {
//
[Link](i - 1, j, i + 1, j, DIV_LINE_COLOR);
}
}
for (i = SCREEN_VERTICAL_RESOLUTION; i > 13; i -= DIVISION_SIZE) {
//
for (j = 0; j < SCREEN_HORIZONTAL_RESOLUTION; j += ((i == 120) ? SUBDIVISION_SIZE
: DIVISION_SIZE)) {
//
[Link](j, i - 1, j, i + 1, DIV_LINE_COLOR);
}
}
// draw current wave
if (trigger == 0) {
// display spectrum
uint16_t max_y = 0, max_x = 0;
uint16_t i_0, i_1;
bool hit_max = false;
for (i = 1; i < BUFFER_SIZE / 2; i ++) {
//
if (y[i] > max_y) {
//
max_y = y[i];
max_x = i;
}
}
max_y = max(max_y, EFFECTIVE_VERTICAL_RESOLUTION);
[Link](WHITE);
[Link](1);
for (i = 1; i < SCREEN_HORIZONTAL_RESOLUTION; i ++) {
//
i_0 = (int)((float)i * (float)BUFFER_SIZE / (float)SCREEN_HORIZONTAL_RESOLUTION
/ 2.0);
i_1 = (int)((float)(i + 1) * (float)BUFFER_SIZE /
(float)SCREEN_HORIZONTAL_RESOLUTION / 2.0);
if (hit_max) {
// was in the vicinity of max
i_0 = max_x;
hit_max = false;
} else if ((max_x <= i_1) && (i_0 <= max_x)) {
// is in the vicinity of max
if ((i_1 - max_x) <= (max_x - i_0)) {
//
hit_max = true;
i_1 = max_x;
} else {
//
i_0 = max_x;
21
}
}
bk[i] = SCREEN_VERTICAL_RESOLUTION - (10 + ((float)y[i_0] / (float)max_y) *
(float)(EFFECTIVE_VERTICAL_RESOLUTION - 10));
[Link](
i,
bk[i],
i + 1,
SCREEN_VERTICAL_RESOLUTION - (10 + ((float)y[i_1] / (float)max_y) * (float)
(EFFECTIVE_VERTICAL_RESOLUTION - 10)),
CH1_SIGNAL_COLOR);
if (i % DIVISION_SIZE == 0) {
//
float freq = ((float)i / (float)SCREEN_HORIZONTAL_RESOLUTION *
(float)DT_FS[time_base]) / 2.0;
[Link](i - (freq > 100 ? 8 : 5) - (freq > (int)freq ? 4 : 0),
SCREEN_VERTICAL_RESOLUTION - 7);
[Link](freq, 1);
}
}
// clear previous stats
[Link](7, 19, 150, 9, BACKGROUND_COLOR);
[Link](8, 20);
[Link](WHITE);
[Link](1);
String s;
s = "F: ";
s += (float)max_x / (float)BUFFER_SIZE * (float)DT_FS[time_base];
s += "kHz ";
s += (float)20 * log10(max_y);
s += "dB";
[Link](s);

} else {
// display time samples
uint16_t maxy = 0;
uint16_t miny = ADC_RESOLUTION;
uint32_t avgy = 0;
for (i = 1; i < BUFFER_SIZE; i ++) {
//
maxy = max(maxy, data16[i]);
miny = min(miny, data16[i]);
avgy += data16[i];
}
avgy /= BUFFER_SIZE;
for (i = 0, j = 0; j < SCREEN_HORIZONTAL_RESOLUTION; i ++, j += h) {
//
bk[i] = SCREEN_VERTICAL_RESOLUTION - (20 + (data16[i] * ADC_SCREEN_FACTOR));
bk[i + 1] = SCREEN_VERTICAL_RESOLUTION - (20 + (data16[i + 1] *
ADC_SCREEN_FACTOR));
[Link](
j,
bk[i],
j + h,
bk[i + 1],
CH1_SIGNAL_COLOR);
if (h > 1) [Link](j, bk[i] - 1, GREEN);
}
// clear previous stats
[Link](7, 19, 60, 9, BLUE);
[Link](8, 20);
[Link](WHITE);
[Link](1);
String s;
s = "Max: ";
22
s += (float)maxy / (float)ADC_RESOLUTION * VCC_3_3;
s += "V";
[Link](s);

[Link](SCREEN_HORIZONTAL_RESOLUTION / 2 - 30, SCREEN_VERTICAL_RESOLUTION -


20, 60, 9, BLUE);
[Link](SCREEN_HORIZONTAL_RESOLUTION / 2 - 29, SCREEN_VERTICAL_RESOLUTION -
19);
[Link](WHITE);
[Link](1);
s = "Avg: ";
s += (float)avgy / (float)ADC_RESOLUTION * VCC_3_3;
s += "V";
[Link](s);

[Link](7, SCREEN_VERTICAL_RESOLUTION - 20, 60, 9, BLUE);


[Link](8, SCREEN_VERTICAL_RESOLUTION - 19);
[Link](WHITE);
[Link](1);
s = "Min: ";
s += (float)miny / (float)ADC_RESOLUTION * VCC_3_3;
s += "V";
[Link](s);
h2 = h;
}

state = 5;
}
if (state == 5) {
//
if (bTitleChange) {
// title change
bTitleChange = false;
[Link](0, 0, SCREEN_HORIZONTAL_RESOLUTION, 12, CH1_SIGNAL_COLOR);
[Link](8, 3);
[Link](BLUE);
[Link](1);
String s = "CH1 ";
s += .65;
s += "V ";
if (trigger == 0) {
// spectrum
s += (int)DT_FS[time_base];
s += "kHz ";
} else {
// time samples
s += DT_DIV[time_base];
s += "us ";
}
if (trigger == 1) {
// raising front trigger
s += "Raising ";
} else if (trigger == 2) {
// descending front trigger
s += "Falling ";
} else if (trigger == 3) {
// no trigger
s += "None ";
} else {
// spectrum scope
s += "Spectrum ";
}
[Link](s);
if (freeze) {
//
23
[Link](170, 3);
[Link](RED);
[Link](1);
[Link]("Freeze");
}
[Link](215, 3);
[Link](BLACK);
[Link](1);
[Link]("[Link]");
}
if (freeze == 3) {
//
freeze = 1;
export_to_sd();
bScreenChange = true;
}
}

delay(50);
state = 1;
}

Al presionar el botón Congelar, también se activa la exportación de datos SD, si hay


una tarjeta SD presente. Por el momento, la exportación se realiza en formato de
texto. El archivo contiene las 1024 muestras adquiridas, los 512 intervalos de
frecuencia calculados, así como la frecuencia de muestreo. Cada archivo se crea en
una carpeta dedicada, utilizando nombres de archivo con el sufijo Índices sucesivos.

La conclusión

Si la hay, es que un proyecto como este nunca está completo. Siempre falta algo, algo
parcialmente implementado o un truco que mantiene las cosas en su lugar. No
obstante, esto se convirtió en una herramienta independiente seria que ya me ayuda
a depurar otros proyectos basados en MCU o sondear señales de audio.
¡Quédate para estar al tanto de las novedades!

24
25

También podría gustarte