Scope Noob: accesorios para microcontroladores con DDS

En esta entrega de Novato del alcance Trabajo con síntesis digital directa usando un microcontrolador. Me sorprendieron gratamente algunas de las rarezas que descubrí durante este proceso. En particular, tuve la oportunidad de ver un disparador de error resuelto mediante el uso de un retraso y algunas peculiaridades de tiempo introducidas por mi uso del microcontrolador. Aquí hay una sinopsis en video, pero cubriré todo en profundidad después del descanso.

Síntesis Digital Directa

DDS es un método para crear señales analógicas a partir de un conjunto de entradas digitales. Me inspiré a probarlo después de ver [Bil Herd’s] publicación y video en DDS. Pero usó lógica programable y pensé en probarlo con un microcontrolador.

Escalera R/2R

Una simple escalera R/2R es la clave de este experimento. Es una red de resistencias en una proporción de 1:2 entre sí. Recomendaría apuntar a resistencias de 10k y 20k; lo que tenía convenientemente eran 500 y 1k que funcionaron bien para esto. Ocho bits del microcontrolador mueven las entradas digitales de la escalera, con una única salida que es una tensión analógica entre 0V y Vcc.

Mis ejemplos codificados para cada uno de estos experimentos están disponibles en este repositorio. El código fue escrito para ATmega328p (que es el Trinket Pro) pero es lo suficientemente simple como para transportarlo fácilmente para cualquier cosa. Mi primera ejecución es una señal de pendiente simple que usa un código que se ejecuta a través de una variable de 8 bits y también se usa como valor de salida. Cuando se desborda, la rampa comienza de nuevo.

uint8_t counter = 0;
while(1) {
  PORTC = counter;
  PORTB = counter >> 6; //Shift the counter so MSB is on PB0 and PB1

  ++counter;
}

Observe que hay varias señales amarillas en esa señal de rastreo. Hablaré de eso después de un tiempo, pero con un "hola mundo" en funcionamiento para el DDS, quería refinar mis métodos usando interrupciones de hardware. Configurar Timer2 a la frecuencia de reloj del sistema de 16 MHz con una resolución de contador de 256 bits todavía me permite generar una frecuencia de 440 Hz:

ISR(TIMER2_COMPA_vect)
{
  static uint8_t counter = 0;

  PORTC = counter;        //Set PORTC
  PORTB = counter >> 6;   //Set PORTB

  counter++;
}

Vale la pena señalar que esos destellos en la señal todavía están allí, solo que son un poco más difíciles de ver en la captura de pantalla de arriba. Como puede ver, esta señal tiene una frecuencia de 438 Hz, lo suficientemente cerca de mi objetivo de 440 Hz. Esa frecuencia es "A" en tono. Supongo que eventualmente podría convertir esto en un proyecto de sonido y, si eso sucede, querría una señal que suene mejor que una onda de rampa.

vi el seno

Este arreglo está bien preparado para generar señales más interesantes. Todo lo que se necesita es un mejor conjunto de datos para enviar a los puertos que un contador creciente. La forma más común de hacer esto es usar una tabla de búsqueda. Encontré un sitio que generará valores de onda sinusoidal en función de sus necesidades paramétricas. Cargué uno con 256 bits de resolución para poder usar el contador de desbordamiento como índice de la matriz.

ISR(TIMER2_COMPA_vect)
{
  static uint8_t counter = 0;       //Declare static this will only be instatiated the first time

  PORTC = sine[counter];            //Set PORTC
  PORTB = (sine[counter++]) >> 6;   //Set PORTB and increment the counter
}

Esa es una curva bastante agradable, pero ahora todavía vemos esas anomalías de señal y es algo nuevo. Cuando amplío la forma de onda, a veces obtengo una señal doble. Esto parece una onda sinusal invertida y superpuesta a sí misma.

Sin saber qué estaba causando esto, señalé [Adam Fabio] para ver si tiene alguna idea. Mencionó que probablemente se trataba del ámbito de acción cuando no debería serlo.

El DS1054z que uso tiene dos configuraciones en el menú de activación que pueden ayudar con eso. El primero es un conmutador que le dice al amplificador que ignore el ruido cuando se inicia. Pude hacer que esto funcionara un poco dependiendo de mi base de tiempo y voltaje de activación, pero no era una solución segura. La otra opción era Holdoff.

Aprendiendo sobre Holdoff

Holdoff es una función que le permite marcar en una ventana de "apagón" donde el visor no funcionará. La mejor discusión sobre la característica que encontré es [Dave Jones’] vídeo sobre la retención. Muestra varios casos de uso, pero el mío es uno de los más fáciles. Sé el tiempo que está usando mi señal y calculé que una parada de poco más de 2,1 ms debería ser suficiente para que la amplitud comience a funcionar al comienzo de cada período.

Separación de puertos de microcontrolador

Finalmente es hora de averiguar qué está pasando con esos puntos en la señal. Aquí pueden ver que estoy midiendo uno de esos puntos, que tiene aproximadamente 1.024 us de ancho. En realidad, eso es bastante, así que comencé a investigar a través del canal dos para ver qué estaba pasando. Muy rápidamente descubrí que las señales siempre ocurren entre el bit 5 y el bit 6 de la escalera R/2R.

En este caso, estoy muy contento de haber usado Trinket Pro porque, de lo contrario, no tendría estos puntos para jugar. Normalmente usaría los ocho bits en un solo puerto, pero esta placa no ofrece eso. Por eso uso PC0-PC5 y PB0-PB1. El soplo se produce debido a la latencia entre la escritura de PORTC y la escritura de PORTB.

El código C que escribí en la rutina de servicio de interrupción es un código C muy conciso y hermoso. Pero el conjunto generado por él muestra por qué obtengo grandes destellos:

Puede ver que hay muchas instrucciones entre escribir en PORTC y luego escribir en PORTB. Esto fue simplemente un seguimiento debido a mi sondeo usando el alcance. La mejor parte es que no tiene que escribir su propio ensamblaje, sino que puede crear su código C teniendo en cuenta el tiempo:

ISR(TIMER2_COMPA_vect) {
  static uint8_t counter = 0;

  static uint8_t prewindC = 0;
  static uint8_t prewindB = 0;

  PORTC = prewindC; //SET PORTC
  PORTB = prewindB; //SET PORTB

  prewindC = sine[counter];
  prewindB = (sine[counter++]) >> 6;
}

En aras de la brevedad, aquí está el conjunto de resultados y la nueva medición de señal (haga clic para ampliar):

Solo se escribe una instrucción entre puertos. ¡Este punto es aproximadamente 5 veces más corto que antes!

¡El resultado de codificar con el microcontrolador en mente acorta la señal unas 5 veces! Es posible reducir aún más esto a la mitad usando un ensamblado en línea, ya que hay una llamada de instrucción más entre las entradas del puerto. Pero creo que este es un maravilloso ejemplo de un osciloscopio que le ahorra tiempo en la resolución de problemas.

Diatriba de selección de microcontroladores

Obviamente, trabajar con un chip desnudo en lugar de una placa rota me habría permitido usar 8 bits en un puerto. Pero supongamos que tuvo que trabajar con esta restricción. Con la mayoría de los controladores de 8 bits, encontrará otro entendimiento que he experimentado aquí: para escribir en PORTB usando una instrucción, tengo que borrar todo el registro de PORTB aunque esté escribiendo solo 2 bits.

La razón de esto es que no puede escribir tanto el 1 digital como el 0 digital en el puerto usando instrucciones bibbit. Lo mejor que puede hacer es primero establecer sus bits objetivo en un valor conocido, luego use una segunda instrucción para escribir los valores objetivo que está buscando. Obviamente, si estás lidiando con el tiempo, esto no es óptimo.

La mayoría de los microcontroladores de 32 bits (y algunas variantes de 8 o 16 bits) tienen una solución para esto. Arriba hay un párrafo de la hoja de datos de TI para el parche TM4C123. Muestra que este procesador permite la escritura de instrucciones individuales sin reemplazar todo el puerto porque tiene una máscara en las piezas superiores del registro. Puede usar un operador de asignación y solo se verán afectados los bits establecidos en el registro de máscara.

Tarea

Todavía no tengo un plan firme para la próxima semana, así que no puedo decírtelo. Ayúdame sugiriéndome un área para explorar dejando un comentario a continuación.

  • Miguel dice:

    Esta serie será de gran valor para mí, especialmente como propietario reciente de un modelo del mismo tamaño.

    • Stu dice:

      Curiosamente, ¡debería conseguir uno para Navidad también!
      Realmente no es sorprendente, ya que su mismo tamaño HaD ayudó a popularizarse hace algún tiempo, debido a su capacidad para desbloquear muchas funciones y mejoras excelentes.

      *** En esa nota, ¿alguien sabe dónde podemos encontrar una página de hack de pernos que funcione? Solía ​​​​estar en un búho, pero luego lo sacaron.

  • Brett Faav dice:

    ¿Alguien puede explicar esta línea de código? ¿Dónde se declara la variable gt? ¿Se usa el anillo amp como un bit AND o una dirección de un operador en cada línea? ¿Cómo es "6;" línea de código C?

    PORTB = contador >> 6; // Mueva el contador para que MSB esté en PB0 y PB1

    • gannon dice:

      El operador >> es un poco una operación de cambio.
      Por ejemplo, si contador = 0b11111100, contador >> 6 es 0b00000011

      • mike szczys dice:

        Para más profundo siempre disfruto [Dean Camera’s] tutorial: http://www.avrfreaks.net/forum/tut-c-bit-manipulation-aka-programming-101

    • tekkieneet dice:

      Los que ve son la imagen del navegador ">" que no forma parte del código. Lo leí también (usando Opera) antes, pero parecían estar corregidos. fue entretenido

      • mike szczys dice:

        Sí lo siento. Creo que el editor de WordPress wysiwyg a veces explota el >> cuando se está editando una publicación. Los arreglé poco después de que se publicara esto.

  • azida_0x37 dice:

    ¡Gran segmento! ¿Qué pasa con la captura de señales transitorias? Parece un siguiente paso lógico.

  • gannon dice:

    Maravilloso artículo. Scope definitivamente ayudaría con mi proyecto Trinket (http://la-tecnologia.io/project/3439-dafunc)
    Aunque espero usar ambos y R2R DAC y filtro de paso bajo para más salidas

  • Sheldon dice:

    Otra forma de minimizar el problema habría sido colocar las piezas adicionales en el extremo LSB de la escalera L / 2R para que cualquier retraso en la actualización tenga un impacto menor en el valor resultante porque esas piezas diferentes representan un cambio de voltaje más pequeño.
    Tomando un caso simple donde el valor aumenta en '1' (que debería ser 1/256 de Vcc - 0.004Vcc), cambiando de b00000011 a b00000100, mirando los 3 bits inferiores podría resultar en una secuencia 011-> 111-> 100 . o 011-> 000-> 100 (dependiendo del orden de escritura del puerto) que es solo igual a una diferencia de voltaje delta de aproximadamente 0.012-0.016 de Vcc.
    Sin embargo, como en la implementación actual que se cambia de B00111111 a B01000000 toma posibles transiciones de B0011111-> B0111111-> B01000000-> B01000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000-> B010000000- > > b010000000-> b010000000-> b010000000-> b010000000-> b010000000-> b0100000000000004b01111111-> b01000000. (ver el problema masivo en el [Mike]fotos de).

    Ah, y mi ejemplo ni siquiera toma un ejemplo peor (donde un conjunto de bits cruza entre cero y el conjunto completo mientras que el otro va en la otra dirección).

    • mike szczys dice:

      Lo pensé, pero pensé que causaría el retraso con más frecuencia. Supuse que sería peor que tenerlo al final de MSB, pero no intenté confirmarlo.

      • Código Rojo dice:

        ¿Qué hay de agregar condensadores a los dos bits de repuesto como un retraso y encenderlos primero?

      • Sheldon dice:

        Supongo que depende de la aplicación (y qué tipo de circulación pones en la salida) porque, sí, si pasas por cada valor, obtienes más errores (aunque un poco más pequeños) pero cualquier cambio al menos siempre es más cerca del objetivo. valor.
        Si sabe que rara vez usaría los valores de MSB, entonces puede ser más agradable ir con una gran falla rara pero, en un escenario en el que los omite todos. sobre la ubicación, la precisión puede ser más apreciada, en cuyo caso tenerlos en el LSB podría ser mejor.

        Estaba pensando en una aplicación de ejemplo: DAC de audio: debido a que es audio, significa que hay un límite de frecuencia para que uno pueda elegir una red RC adecuada de modo que "salve la brecha" cuando los valores salten (efectivamente filtro de paso bajo). Sería interesante ver cómo sonaría realmente la diferencia entre las cuatro configuraciones (MSB versus LSB y con/sin filtro de paso bajo; de hecho, cuál es la frecuencia más alta que se puede lograr como no se debe). no necesita el mismo filtro RC).

      • david galloway dice:

        Creo que encontraría que tenerlos en las piezas significativas más bajas sería mejor incluso si ocurrieran con más frecuencia. También tiene razón en que puede eliminar esa carga adicional entre el almacenamiento en los dos puertos si lo desea. En tercer lugar, otra opción sería bloquear los ocho bits completos con un perno de ocho bits y cronometrar todo a la vez.

  • Sylph-DS dice:

    Realmente, ¿qué quieres, ya sea tener todos los pines en el mismo puerto (estos puertos son de 8 bits, verdad?), o sin éxito, algún tipo de perno entre el DAC y el µC. La mayoría de los circuitos integrados DAC R2R tienen un perno solo para este propósito.

    • SavannahLeono dice:

      Un perno funcionaría en este caso como complemento, pero no como el único puerto en esta placa específica. No me molesté en mirar el esquema, pero el autor dice que un puerto completo no está disponible.

      Si está diseñando una placa de principio a fin, estas son sugerencias que debe considerar seriamente. Si está trabajando en hardware existente, estos se convierten en una lista de deseos.

  • arácnido dice:

    Arreglar esto en el hardware, por supuesto, es usar un perno que le permita actualizar las piezas a su gusto, y luego cambiarlas todas a la vez con el perno.

  • trui dice:

    forma más simple / más pequeña de hacerlo, evitando valores "estáticos" innecesarios:

    valor = pecado[counter++];
    PUERTOB = valor >> 6;
    PORTC = valor;

  • arácnido dice:

    Además, si se toma en serio el DDS, en lugar de volver a calcular el gráfico SIN para cada frecuencia, puede adoptar un enfoque similar a lo que hacen los chips DDS reales: tener un registro de fase. Algo como esto:

    frecuencia interna = 123;
    fase int = 0;
    tonto (1) {
    fase + = frecuencia;
    salida (forma de onda[phase >> 8])
    }

    Esto supone entradas de 16 bits para fase y frecuencia, y una tabla de formas de onda de 8 bits. Los diferentes anchos de bits y la interpolación entre muestras se dejan como ejercicio para el lector. 😉

  • dainbramage dice:

    Cuanto más veo el 1054Z en acción, más deseo no haber comprado mi 1102E el año pasado y estar esperando el nuevo. Por supuesto, no tenía forma de saber que Rigol exhibiría un visor tan mejorado al mismo precio. Oh no. Mi tamaño hace casi todo lo que necesito y es mucho mejor que el Tektronix 453 al que reemplazó.

    ¡Continúe con esta serie! Aunque he estado usando ámbitos aquí y allá desde que era un adolescente, estoy aprendiendo algo nuevo con cada tema.

    • Markus Hermle dice:

      A mi igual me duele :/
      Pero antes trabajé con una solución fea y LEDs para adivinar cosas, con eso controlé el Tiempo.

    • rasz_pl dice:

      para eso es ebay, vendo actual, pero nuevo, sencillo

  • tekkieneet dice:

    El siguiente paso sería analizar la forma de onda utilizando la FFT del entorno. Mire el contenido de las armonías a medida que aumenta la frecuencia del DDS y/o cambia los tipos de forma de onda. También sería interesante ver las armonías anteriores antes y después del cambio en el código. Este problema aparecería como un pequeño pico en la frecuencia del bucle de interrupción.

    • mike szczys dice:

      Esto es lo que [Bil Herd] sugirió. Lo comprobaré. ¡Gracias!

  • Darren dice:

    "generar un período de 440 Hz:". Período es un intervalo de tiempo, Hz es una frecuencia.

    • andrewjhull dice:

      Me acabas de dar en eso... el período sería de 1/440 segundos o alrededor de 0,00227 segundos.

    • mike szczys dice:

      Así es, lamento escuchar eso.

  • Mateo Everitt dice:

    Creo que nunca lo había intentado antes, que definir las variables 'prewind' como un registro (register uint8_t prewindC = 0;) debería eliminar la carga adicional entre las dos salidas. Obviamente, necesitaría otro conjunto de variables estacionarias y aún habría un solo ciclo de reloj entre las dos configuraciones de salida.

    Por supuesto, para una función suave, un filtro RC simple podría ser extremadamente útil.

    • trui dice:

      Simplemente eliminar el 'todavía' debería ser suficiente para que el compilador use registros. Mira el código que escribí antes.

  • klaus dice:

    Use una tabla de búsqueda para un período completo de los valores de pecado, esto ahorra tiempo. Incluso puede guardar solo los valores para un segmento de 90 grados si cambia la dirección de cálculo y el signo de valor. Eso ahorra memoria.

    • gannon dice:

      Idea interesante. Si me encuentro con problemas de límite de memoria con mi proyecto, tendré que investigarlo.

  • tekkieneet dice:

    FYI: La parte inferior de la pista azul (primera pista de amplitud, ampliada) es una indicación de que el tiempo de subida/bajada de la señal es demasiado rápido para usar el clip de tierra largo en su sonda de amplitud. Reduzca el área del bucle de la conexión a tierra (con un accesorio de resorte de tierra corto en la sonda) y verá que desaparece.

  • elliot williams dice:

    No sabe lo que está conduciendo a continuación en su cadena de señal, pero probablemente terminará almacenando en búfer la salida del R2R DAC, que es un buen momento para reducir la señal analógica y hacerla incluso "analógica".

    Por supuesto, entonces nunca verías tus errores ni solucionarías el problema original...

    Pero tenías que hacer esto en los viejos tiempos con chips DAC que tenían transitorios cambiantes terribles.

  • fagocitado dice:

    Esta también es una buena prueba de lo que un compilador le hace a su código C. No es necesario mantener el orden o el tiempo, solo garantiza un equivalente numérico de la salida. Está tratando de usar la sugerencia de "registro" para persuadirlo de que use dos registros. En última instancia, no debería ser escuchado porque eso es solo una sugerencia. Para estar seguro de la sincronización y tener un compilador/código de estado de ánimo menos dependiente, probablemente necesitará usar un ensamblado en línea para la tarea final a portar. La 'pre-venta' no debería ser necesaria y deberías evitar las variables estáticas tanto como sea posible. Las variables estacionarias generalmente requieren una ventaja adicional significativa. Aquí has ​​introducido dos más para reducir el número de operaciones entre tareas, pero parece que hay mejores opciones... Soy fan de la columna; ¡sigan con el buen trabajo!

    • fagocitado dice:

      erróneamente, debería decir "usted podría intentar usar la sugerencia del compilador 'registro' para persuadirlo de que use dos registros".

      • trui dice:

        La palabra clave "registrar" no ayudará siempre que las variables se declaren "estacionarias". La combinación de registro estático no es compatible.

  • carl w livingston dice:

    Me escondo aquí a menudo, dos o tres veces al día, exactamente, pero nunca entré en una discusión. Pero en este caso, pensé en ofrecer información que podría facilitarme la vida cuando miro formas de onda inestables con un osciloscopio.

    La puesta en marcha confiable en formas de onda complejas (e incluso ondas sinusoidales básicas) es un problema a largo plazo. Con la llegada de los microprocesadores y los microcontroladores, un método simple de sincronización en una forma de onda es utilizar la función de activación externa de su entorno.

    Básicamente, cuando su forma de onda DDS alcanza cierto punto, dígale al microcontrolador que cambie la línea IO en el microcontrolador. Esta línea de E/S cambiante se convierte en la entrada de activación externa de su osciloscopio. El disparo DDS IO puede ocurrir en cualquier lugar dentro de la forma de onda, siempre que ocurra en el mismo punto de la forma de onda, TODO EL TIEMPO.

    La teoría es que, con el disparador interno, hay un ligero retraso entre el momento en que la forma de onda activa el disparador interno y cuando la amplitud inicia la pista en la pantalla de visualización, la forma de onda puede tener mucho ruido sobre la forma de onda, causando temblor . cuando se ve en una pantalla de magnitud. Y como probablemente haya visto, el problema que sucede es que la oscilación actual no siempre termina mostrándose en la amplitud hasta que la forma de onda entrante alcanza el siguiente nivel de disparo interno y provoca una desalineación de la horizontal. barrer.

    La idea es que desee establecer un disparador de sincronización que cambie el pin IO exactamente en el mismo punto de la forma de onda. Al cambiar (de bajo a alto en un swing, de alto a bajo en el siguiente swing, de bajo a alto en el swing siguiente, etc.) obtienes una señal de disparo cada dos formas de onda, dando el swing horizontal antes del próximo disparo entrante se genera. .

    El tiempo en la forma de onda en el que realmente cambia el pin IO no es tan importante como activar constantemente el pin IO en el mismo lugar en esa forma de onda.

    Entonces, supongamos que está generando una onda sinusoidal usando su sistema DDS. Un buen lugar para cambiar el bit IO podría ser la mitad de la cuenta (0x7F), especialmente si planea compensar la onda sinusoidal generada por DDS a una forma de onda +/- PP, es decir, en lugar de una forma de onda pico a pico de 0 a 5. cabalgando por completo. sobre la Tierra, usted compensa la onda sinusoidal para que se convierta en una forma de onda convencional de +/- 2.5 VAC PP

    La razón por la que cambia la línea IO es que, por su naturaleza, el disparo debe ser una repetición más baja (lectura, frecuencia más baja) que la señal que está observando. Por lo tanto, la conmutación garantiza que se vea cada forma de onda y, por lo tanto, la conmutación de la línea IO garantiza que se cumplan los requisitos de sincronización convencionales para una forma de onda constante y estable.

    Entonces, si, como con AVR, observa una onda sinusoidal generada por una red de escalera R2R de 8 bits conectada a los pines IO de uno de los puertos IO de 8 bits, el rango de valores es 0: 255. Si desea comenzar con lo que eventualmente se convertirá en una verdadera transición de CA cero, consulte los datos de E/S que alimentan la escalera R2R para 0x7F. De lo contrario, cambie la línea de E/S a 0x00 o donde desee, siempre que cambie la línea de E/S a la misma ubicación dentro de la forma de onda.

    Espero que esto ayude.

    CWL

  • Sin dice:

    ¿Cómo obtienes el ensamblaje de tu código C?

  • Mate dice:

    Extraño estas publicaciones. ¡Vendrán más! Acabo de recibir un 1054z, todavía estoy averiguando cómo funciona. Tus publicaciones ayudaron mucho.

  • WillyWonker dice:

    El artefacto parece una oscilación subestimada. Probablemente capacitancia / inducción perdida. Experimente con una disposición física de las partes en la zapatilla. Algo fácil de probar antes de saltar al vudú de ISR.

Ricardo Prieto
Ricardo Prieto

Deja una respuesta

Tu dirección de correo electrónico no será publicada.