audio generador (solucionado)

la idea es armar un electroestimulador pero no con un transformador como elevador de tencion sino con transistores de modo que los pulsos sean mas amigables y no tan violentos como los que entrega la conmutacion de una bobina.....
a muchas personas (como yo, y que necesitamos reavilitacion) nos es imposible soportar los picos ...o no los percibimos o nos resultan insoportables
cuando termine subire las fotos
gracias gente

Quizá quisiste decir "generador de funciones". Porque "audio" como tal, se limita al rango de 20 Hz - 20 KHz (que es el rango de frecuencias que el ser humano promedio alcanza a oír).

La otra pregunta es: ¿qué formas de onda debe generar?

Al ser un sistema digital, lo más sencillo es generar una onda cuadrada (como lo hace tone).
Generar otros tipos de ondas es posible (incluso música), pero involucraría algo de hardware pasivo y programación más avanzada como el uso de PROGMEM, leer datos desde la memoria flash, interrupciones con timer1 y generar PWM sin analogWrite (en espacial si se desea alcanzar altas frecuencias).

Gracias por responder
si el rango que deseo es entre 0.1hz y 200 hz aproximadamente. ...estoy bastante avanzado en este mini proyecto pero si ahora necesito
algunos periféricos para tipo d/a y demás
me gustaría más poder hacer los cambios de frecuencia con pulsadores pero bueno a este Lo haré con potenciometros.
mis conocimientos en programación son muy básicos
aunque se irá aprendiendo
son cuatro señales sierra. cuadrada. sinusoidal. y triángulo.
ahora me queda bajar la frecuencia de salida ...tiene como 0000 más de lo que necesito
jaja

laureanor:
si el rango que deseo es entre 0.1hz y 200 hz aproximadamente.

En ese caso, la frecuencia de interrupciones no será tan alta; mejor dicho: menos carga para el microprocesador

laureanor:
algunos periféricos para tipo d/a y demás

Eso si quieres, pero antes te había comentado de la posibilidad de utilizar el mismo Arduino como DAC.
Existen dos métodos: PWM (más económico y solo requiere de un pin) y la escalera R-2R (para una resolución de 8 bits, se necesitarían 8 pines y 16 resistencias; y amplificación en caso de tener que compensar la enorme impedancia que generan la mitad de resistencias necesarias).

laureanor:
me gustaría más poder hacer los cambios de frecuencia con pulsadores pero bueno a este Lo haré con potenciometros.

De cualquier forma es posible; aunque como estamos en periodo de pruebas, es más práctico el potenciómetro por la única razón de lo sencillo que sería para jugar con la frecuencia.

laureanor:
mis conocimientos en programación son muy básicos
aunque se irá aprendiendo
son cuatro señales sierra. cuadrada. sinusoidal. y triángulo.

En ese caso, sí se requiere de algún DAC (repito, puedes usar el Arduino para tal efecto; pero sí quieres utilizar un DAC externo, está bien).

Al ser señales que un sistema digital "nativamente" no puede crear (en especial la sinusoidal), se necesita del DAC antes mencionado y de "muestras" que sirvan para construir la señal.
El otro tema que hay que debatir, es el de las interrupciones por timer1 y la lectura de la memoria flash.
El asunto de las muestras de las señales me puedo encargar yo.

laureanor:
ahora me queda bajar la frecuencia de salida ...tiene como 0000 más de lo que necesito

Si tan solo supiera qué hiciste ahí...

Que tal esto y no te rompes la cabeza.
Una busqueda en Google : arduino generator da este link Arduino DDS Sinewave Generator

me enseñas a subir el code para explicar mejor?

ya no me quedan puertos. ...es decir la única forma de agregar botones sería por A4 (donde entra lectura del potenciometro) ahora si en A4 colocó dos pulsos uno de +5 Y el otro de 0 puede asignarse que ante un 1 sume y un 0 reste?

Como ADC, tienes hasta 1024 posibilidades en un solo pin. Como entrada digital solo tienes dos: acción o no acción. Decir que el estado alto para sumar, y el bajo para restar; haría que constantemente esté haciendo una de las dos.
Para que funcione como debe, sí o sí debes utilizar dos entradas. Hablas de A4, pero ¿y A5?

Lo olvidaba:

La propuesta de surbyte tiene todo lo necesario para generar una onda diferente de la cuadrada. Lo único que le falta, son las muestras para una onda triangular y de sierra.
Muestrear una onda cuadrada no tiene sentido si es lo que "naturalmente" un sistema digital puede generar.

actualmente A4 opera para pwm y A5 frecuencia. ..
Los otros puertos están cambiando entre seno. sierra. triángulo y cuadrada. ..
entonces esta opción es muy buena
dejar por defecto cuadrada y los cambios en lcd que sean para las otras 3 y hay puedo hacerme de un puerto más. ...
estoy entendiendo?
o nada que ver?

Otra opción son los pines 0 y 1; a menos de que se requiera del puerto serie.
No sé, tal parece que hay al menos un par de pines libres; así que tu decides cuáles utilizar.

no me quedan....8 a 13 lcd... 0 a 7 la salida ....de 0 a 3 cambio de onda 4 y 5 pwm y frecc....

 void checkFreq() {
 freqCurrent = analogRead(A5);
 if (abs(freqCurrent-frequency)>freqTolerance){
   frequency = freqCurrent;//NUEVA FRECUENCIA
   freqscaled = 108*frequency+1;// 1 to ~108,000---------------------------------------------------------------------------------------------------
   period = samplerate/freqscaled;
   pulseWidthScaled = int(pulseWidth/1023*period);
   triInc = 511/period;
   if (triInc==0){
     triInc = 1;
   }
   sawInc = 255/period;
   if (sawInc==0){
     sawInc = 1;
   }
   sinInc=20000/period;
 }
 lcd.setCursor(0,1);
 lcd.print(freqscaled/300.0 ); //___________________________________________________________________________pote
 lcd.print("Hz ");

 void checkShape() {
 typelast = typecurrent;
if (digitalRead(A0)==LOW){
 typecurrent  = 1;
 lcd.setCursor(5,0);
 lcd.print(" Cuadrada    ");
}
else if (digitalRead(A1)==LOW){
 typecurrent = 2;
 lcd.setCursor(5,0);
 lcd.print(" Triangular  ");
}
else if (digitalRead(A2)==LOW){
 typecurrent = 4;
 lcd.setCursor(5,0);
 lcd.print(" D. Sierra   ");
}
else if (digitalRead(A3)==LOW){
 typecurrent = 8;
 lcd.setCursor(5,0);
 lcd.print(" Sinusoidal      ");
,

si logro dejar por defecto que comience con un tipo de onda esto me dejaria opcion a liberar un pin adicional y con eso resolveria ...incrementar o bajar la frec por pulsador ...?????
suena fasil...si se sabe como...
por mi parte estoy atascado....

laureanor:
0 a 7 la salida ....de 0 a 3 cambio de onda

¿8 pines para la salida? ¿Prefieres usar el método R-2R en vez de un único pin PWM?
Tres entradas para seleccionar las formas de onda, pensé que era sólo una que ciclaba entre las opciones.

Si realizaras mis sugerencias, te ahorrarías 9 pines (2 si insistes con R-2R).

Respecto a ese código... no sé si realmente generaría las ondas como tiene que ser; yo preferiría optar por valores precalculados (muestras).

El tema de las interrupciones: podemos hacerlo de modo difícil, o con TimerOne

....

const byte sine20000[] PROGMEM =¨{ 126, 126,acorto los numeros por exeso de caracteres 126, 126,};

//wavetype storage:
//0 is pulse
//1 is triangle
//2 is saw
//3 is sine
byte type = 0;//initialize as square
byte typecurrent = 8;
byte typelast;
byte typecounter[4];
byte i;

//variables for PW pot monitoring
float pulseWidth;
int pulseWidthScaled;
int PWCurrent;
byte PWTolerance = 8;//adjust this to increase/decrease stability of PW measurement

//variables for freq pot monitoring
int frequency;
int freqCurrent;
byte freqTolerance = 2;//adjust this to increase/decrease stability of frequency measurement
unsigned int freqscaled;

byte wave;
long t;
long samplerate;
long period;

//storage variables- I used these to cut down on the math being performed during the interrupt
int sawByte = 0;
byte sawInc;
int triByte = 0;
byte triInc;
int sinNum = 0;
int sinInc;

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
//connect pins LCD>ARDUINO = RS>D8, E>D9, D4>D10, D5>D11, D6>D12, D7>D13 THIS CAN BE REARRANGED HOWEVER YOU LIKE
void setup() {
  lcd.begin(16,2);
  lcd.print ("kinesiologia    ");
  lcd.setCursor(0,1);
  lcd.print ("GARDEL          ");
  //set port/pin  mode
  DDRD = 0xFF;//all outputs PINS 0-7
  DDRC = 0x00;//all inputs  PINS ADC
  //DDRB = 0xFF;//all outputs PINS 8-13 DISABLED FOR LCD USE
  //TIMER INTERRUPT SETUP
  
  cli();//disable interrupts
  //timer 1:
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  //set compare match register- 100khz to start
  OCR1A = 159; // = (16 000 000 / 100 000) - 1 = 159
  //turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 bit for 0 prescaler
  TCCR1B |= (1 << CS10);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A); 
  
  samplerate = 100000;
  
  //PORTB = 0;        //DISABLED FOR LCD USE
  //PORTB = 1<<type;  //DISABLED FOR LCD USE
  
  //initialize variables
  frequency = analogRead(A5);//initialize frequency
  freqscaled = 98*frequency+1;//from 1 to ~50,000\ //-------------------------------------------------------------------------------------------
  period = samplerate/freqscaled;
   
  pulseWidth = analogRead(A4);//initalize pulse width
  pulseWidthScaled = int(pulseWidth/1023*period);
  
  triInc = 511/period;
  sawInc = 255/period;
  sinInc = 20000/period;
  
  sei();//enable interrupts
  delay (5000); // So we can see the nice splash screen
  lcd.setCursor(0,0);
  lcd.print("Onda:           ");
}

void checkFreq() {
  freqCurrent = analogRead(A5);
  if (abs(freqCurrent-frequency)>freqTolerance){
    frequency = freqCurrent;//NUEVA FRECUENCIA
    freqscaled = 108*frequency+1;// 1 to ~50,000---------------------------------------------------------------------------------------------------
    period = samplerate/freqscaled;
    pulseWidthScaled = int(pulseWidth/1023*period);
    triInc = 511/period;
    if (triInc==0){
      triInc = 1;
    }
    sawInc = 255/period;
    if (sawInc==0){
      sawInc = 1;
    }
    sinInc=20000/period;
  }
  lcd.setCursor(0,1);
  lcd.print(freqscaled/300.0 ); //___________________________________________________________________________pote
  lcd.print("Hz ");
}

void checkPW() {
  PWCurrent = analogRead(A4);
  if (abs(PWCurrent-pulseWidth)>PWTolerance){//if reading from pot exceeds tolerance
    pulseWidth = PWCurrent;//new pulse width, val between 0 and 1023
    pulseWidthScaled = int(pulseWidth/1023*period);
  }
  lcd.setCursor(9,1);
  lcd.print(PWCurrent/11);
  lcd.print("/Pwm ");
}


void checkShape() {
  typelast = typecurrent;
if (digitalRead(A0)==LOW){
  typecurrent  = 1;
  lcd.setCursor(5,0);
  lcd.print(" Cuadrada    ");
}
else if (digitalRead(A1)==LOW){
  typecurrent = 2;
  lcd.setCursor(5,0);
  lcd.print(" Triangular  ");
}
else if (digitalRead(A2)==LOW){
  typecurrent = 4;
  lcd.setCursor(5,0);
  lcd.print(" D. Sierra   ");
}
else if (digitalRead(A3)==LOW){
  typecurrent = 8;
  lcd.setCursor(5,0);
  lcd.print(" Sinusoidal      ");
}

  for (i=0; i<4; i++){
    if (i==type){
    }
    else{
      if ((typecurrent & (1 << i)) ^ (typelast & (1 << i))){//current diff than prev and debounce
        if ((typecurrent & (1 << i))){//currently depressed
            type = i;//set wave type
        }
        else {
          typecounter[i] = 12;//else set debounce counter to 12
        }
      }
      else if (((typecurrent & (1 << i)) == (typelast & (1 << i )))) {//if current same as prev and diff than debounce
        if (typecounter[i] > 0 && --typecounter[i] == 0) {//decrease debounce counter and check to see if = 0
          if ((typecurrent & (1 << i))){//if debounce counter = 0 toggle debounced state
            type = i;
          }
        }
      }
    }
  }
}
    
 

ISR(TIMER1_COMPA_vect){//timer 1 interrupt
  //increment t and reset each time it reaches period
  t += 1;
  if (t >= period){
    t = 0;
  }
  switch (type) {
    case 0://pulse
    if (pulseWidthScaled <= t) {
      wave = 255;
    }
    else{
      wave = 0;
    }
    break;
    case 1://triangle
    if((period-t) > t) {
      if (t == 0){
        triByte = 0;
      }
      else{
        triByte += triInc;
      }
    }
    else{
      triByte -= triInc;
    }
    if (triByte>255){
      triByte = 255;
    }
    else if (triByte<0){
      triByte = 0;
    }
    wave = triByte;
    break;
    case 2://saw
    if (t=0){
      sawByte=0;
    }
    else{
      sawByte+=sawInc;
    }
    wave = sawByte;
    break;
    case 3://sine
    sinNum = t*sinInc;
    wave = pgm_read_byte_near(sine20000 + sinNum);
    break;
  }

  PORTD = wave;
}

void loop() {
  checkFreq();
  checkShape();
  checkPW();
  //PORTB = 1<<type; //DISABLED TO USE WITH LCD
}

Aquí te dejo la base de este programa generador de funciones; es un adjunto porque no solo contiene el sketch, sino que además contiene otros archivos necesarios.

Algunos detalles por destacar:

//#define USE_TIMER2 // Descoméntalo para usar el timer2 y no el timer0; así salvamos a delay y a millis, en caso de ser necesarios.

Si no requerirás de dichas funciones, déjalo así.

#define BOTON_CAMBIO 2

Aquí se define a qué pin irá el botón que cambia la forma de onda. Las líneas que inician con '#', no deben terminar con ';'

#ifdef USE_TIMER2
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  analogWrite(3, 1);
  //analogWrite(11, 1);
#else
  pinMode(5, OUTPUT);
  //pinMode(6, OUTPUT);
  analogWrite(5, 1);
  //analogWrite(6, 1);
#endif

Hay un par de líneas comentadas; se debe a que solo se necesita un pin del par.
Nótese que si usará el timer2, los posibles candidatos son el 3 y 11; 5 y 6 con el timer0.

pinMode(BOTON_CAMBIO, INPUT_PULLUP); // Es accionado cuando se conecte a tierra.

Tomar en cuenta lo que dice el comentario.

#ifdef USE_TIMER2
      //OCR2A = pgm_read_byte(&seno[count++]);
      OCR2B = pgm_read_byte(&seno[count++]);
#else
      //OCR0A = pgm_read_byte(&seno[count++]);
      OCR0B = pgm_read_byte(&seno[count++]);
#endif

Similar al caso de los analogWrite. Aquí lo que sucede es que dependiendo del pin que se vaya a utilizar como salida de señal, hay que modificar el registro correcto; de lo contrario no pasa nada.

Entre pin PWM y la salida final, debe haber un filtro pasa-bajos de por medio; o en el osciloscopio solo verás una onda cuadrada de aprox. 65 KHz (o nada si no se modifica el registro correcto; ver observación anterior). Al menos para aplicaciones de audio, me han funcionado dos resistencias de 100 ohms y un capacitor de 0.1 uF.

GeneradorFunciones.zip (3.2 KB)

miles de gracias. ...
mañana (acá van a ser las 3am) le meto mano y veo como lo íntegro al que estoy haciendo
(necesario dejar lcd) ...ademas me sirve como aprendizaje. ...nada mejor que renegar largo rato para fijar información. ...
abrazo
PD. luego comentó resultados!

intentare explicar lo mas claramente posible,
como dice surbite es inentendible si se esta del otro lado y se intenta comprender lo que escribi
ok....
una banda de 0,1 hz a 200 hz
con cuatro tipos de señales :cuadrada....senusoidal, sierra, triangulo
de existir la posibilidad : una funcion que por un segundo envie una de las cuatro señales a una frecuencia asignada
luego queda en reposo por un periodo y vuelve a disparar....
la idea es crear un electroestimulador, hay o existimos personas que tienen una tolerancia muy baja a la electricidad
y los electroestimuladores que se pueden encontrar en el mercado actuan conmutando un transformador, lo que da un pico de tension que es lo que exita el musculo , esos picos son de muy corta duracion y no todos podemos soportar la terapia...en mi caso tube un accidente y deberia hacer reavilitacion, pero es imposible soportar ese tipo de aparatos, ahora existen "los otros" que son hasta agradables y trabajan con verdaderas ondas lo que biologicamente no podria explicarlo , si que puede tolerarse el problema radica que no podes comprar equipos hasta dar con uno que sea con este principio y no con el otro y logicamente no te van a dejar desarmar para ver su construccion antes de comprarlo,
supongo que los profecionales seran todos iguales pero los costos son demaciado elevados
la idea es hacelo a partir de un generador de funciones ......
un abrazo.-

laureanor:
con cuatro tipos de señales :cuadrada....senusoidal, sierra, triangulo

Oh sí, olvidé mencionar que no he implementado la onda cuadrada porque no tiene mucho sentido generarla por PWM (pero posible). Aunque ya sé me ocurrió como generarla sin PWM pero en el mismo pin; código al final del post.

laureanor:
de existir la posibilidad : una funcion que por un segundo envie una de las cuatro señales a una frecuencia asignada
luego queda en reposo por un periodo y vuelve a disparar....

Yo di una "base" para el programa; tú deberías de encargarte del resto. Te daré unas pistas de cómo hacerlo:

  • Aprende sobre el uso de millis; esto implica que sí o sí tendrás que utilizar el timer2 (pin 3 u 11) para la generación de señales (el código del final estará modificado para tal efecto).
  • En la parte del reposo, utiliza una función del código que sirve para "apagar" la señal.
  • Para cambiar la frecuencia, modifica el valor de una variable que luego es utilizada para cambiar el periodo de la interrupción. Ese valor no puede ser menor que la constante MAX, ni mayor que la constante MIN.

laureanor:
la idea es crear un electroestimulador

No conozco mucho del tema, por eso me surgen dos dudas:

  • ¿La onda debe recorrer voltaje negativo (en un "sentido opuesto"), o con solo que recorra el voltaje positivo es suficiente?
  • ¿Con una señal PWM filtrada será suficiente también?

Ahora sí, el código:

/*
   Probado en un ATmega328P a 16 MHz. En otros microcontroladores puede no funcionar correctamente.

   Usar timer0 implica sacrificar delay y millis. Usar timer2 implica sacrificar tone.
   Este programa require el uso del timer1, lo cuál no se recomienda utilizar los pines 9 y 10; tampoco servos.
*/

#include <TimerOne.h> // https://github.com/PaulStoffregen/TimerOne
#include "seno.h" // No es lo que piensan uds xD
#include "triangulo.h"
#include "sierra.h"

#define SAMPLE_RATE 32000 // Frecuencia de muestreo a la que normalmente se escucharía el sonido. Aplica para todas las ondas.

// NO TOCAR ESTAS TRES DEFINICIONES
#define PERIOD (int)(1000000 / SAMPLE_RATE)
#define MIN (PERIOD * 2000) // Señal a 0.1 Hz
#define MAX (int)(PERIOD) // Señal a 200 Hz

#define BOTON_CAMBIO 2
boolean estadoAnt = false; // Evita que se sigan cambiando las ondas mientras se mantiene pulsado
unsigned long periodo = MAX;

volatile byte count = 0; // Índice del array que contiene las muestras del sonido
volatile boolean cambio = false;
volatile byte opcion = 0;

void setup() {
  TCCR2B = TCCR2B & 0b11111000 | 0x01;
  // Prescaler en 1; así consigo una frecuencia de PWM de hasta 65 KHz (CPU @ 16 MHz).
  // Ayuda al filtro RC pasa-bajos a convertir la señal PWM en un "pseudo-DAC" (pero que funciona y muy bien).

  TCCR2A |= 3;

  pinMode(3, OUTPUT); // Dejo el pin 3 para generar las señales, porque creo que mencionaste que el 11 ya estaba ocupado.
  analogWrite(3, 1);

  pinMode(BOTON_CAMBIO, INPUT_PULLUP); // Es accionado cuando se conecte a tierra.
  Timer1.initialize(); // La inicialización que siempre pide la librería
  Timer1.attachInterrupt(writeSample, periodo); // A partir de aquí comienza la reproducción de la onda, a manera de bucle infinito.
}

void loop() {
  boolean estadoAct = !digitalRead(BOTON_CAMBIO);
  if (estadoAct && !estadoAnt) {
    if (opcion == 3) opcion = 0;
    else opcion++;
    cambio = true;
  }
  estadoAnt = estadoAct;
  periodo = map(analogRead(A0), 0, 1023, MIN, MAX);
  Timer1.setPeriod(periodo);
  // Aquí es donde la magia ocurre: el "pitch" (tono+velocidad) del sonido se altera con base en las lecturas analógicas de A0.

}

void writeSample() {
  if (cambio) {
    if (opcion == 3) digitalWrite(3, LOW);
    else analogWrite(3, 1);
    cambio = false;
    count = 0;
  }

  switch (opcion) {
    case 0: // Onda senosoidal
      //OCR2A = pgm_read_byte(&seno[count++]);
      OCR2B = pgm_read_byte(&seno[count++]);
      break;

    case 1: // Onda triangular
      //OCR2A = pgm_read_byte(&triangulo[count++]);
      OCR2B = pgm_read_byte(&triangulo[count++]);
      break;

    case 2: // Onda sierra
      //OCR2A = pgm_read_byte(&sierra[count++]);
      OCR2B = pgm_read_byte(&sierra[count++]);
      break;

    case 3: // Onda cuadrada
      if (count++ < 80) PORTD |= B00001000;
      else PORTD &= B11110111;
  }

  if (count == 160) count = 0; // Se acabó el array, vuelve al principio otra vez.
}

void apagarSenal() {
  Timer1.detachInterrupt();
  digitalWrite(3, LOW);
}

void encenderSenal() {
  cambio = true;
  Timer1.attachInterrupt(writeSample, periodo);
}