Vamos a ver como utilizando la placa de desarrollo Arduino UNO, podemos generar ondas cuadradas de diferentes frecuencias y Duty Cycle.
Además y muy interesante vamos a poder ver la representación gráfica de la señal de salida por medio del Serial Plotter del IDE de Arduino.
Es importante destacar que la señal generada puede ser utilizada en otros proyectos, el código no solo es teórico, podemos conectar la salida del pin digital a nuestro propio osciloscopio, utilizarla como señal de reloj para algún otro proyecto, etc.
Para entender el funcionamiento del código te invito a ver el video que dejo al final.
La frecuencia máxima teórica que puede generar es de: 1 Mhz (1.000.000 Hz / Ver video)
El Serial Plotter tiene un límite para mostrar la onda graficamente (Ver video)
Librería Necesaria: TimerOne
#include <TimerOne.h>
#include <Wire.h>
/*
Generador de Onda Cuadrada Simple
Ejemplo:
Cuando t=1000 ms la frecuencia es de: 1000Hz.
Cuando K=512 el Duty Cycle es de: 50%
K puede ser desde 0 hasta 1023
*/
unsigned long f , t=1000 , k=512;
byte k1;
void setup()
{
Serial.begin(115200);
// Arduino UNO solo pines 9 y 10 pueden utulizarse
pinMode(10, OUTPUT);
}
void loop()
{
/*
Timer1.initialize(microseconds);
Comiensa a usar el temporizador. Esta función debe llamarse primero.
"microsegundos" es el período de tiempo que tarda el temporizador.
*/
Timer1.initialize(t);
/*
Timer1.pwm(pin, duty);
Configure uno de los pines PWM del temporizador.
El valor de "duty" va de 0 a 1023, donde 0 hace que el pin esté siempre
en BAJO y 1023 hace que el pin esté siempre en ALTO.
*/
Timer1.pwm(10, k);
f=1000000/t; // calculamos la frecuencia
k1=k*100/1024; // calculamos % el Duty Cicle
// Mostramos la informacion en el Monitor Serie
/*
Serial.print("Frecuencia:");
Serial.println(f);
Serial.print("Duty-Cycle:");
Serial.println(k1);
*/
noInterrupts();
Serial.println(myDigitalRead(10));
interrupts();
delay(100);
}
int myDigitalRead(uint8_t pin)
{
uint8_t timer = digitalPinToTimer(pin);
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
if (port == NOT_A_PIN) return LOW;
if (*portInputRegister(port) & bit) return HIGH;
return LOW;
}
Agregando Pulsadores para el control de la Frecuencia y el Ciclo de Trabajo (Duty Cicle)
Si queremos hacer un proyecto definitivo y portátil va a ser necesario agregar pulsadore para modificar la Frecuencia y el Duty Cycle de nuestro generador de onda cuadrada. También necesitaremos visualizar los valores por algún medio.
Entonces en mi caso, he optado por utilizar 4 pulsadores estilo tack y un display de 4 bit de 7 segmentos para visualizar la Frecuencia y el Duty Cycle.
Hay varios frentes abiertos que resolver. El primero es el Display ya que solo puede mostrar 4 dígitos y como recordarán estamos necesitando como máximo 6 dígitos, por ejemplo para mostrar una Frecuencia de 100.000 Hz.
Y por otro lado tenemos dos datos para mostrar y tan solo con 4 pulsadores.
Por supuesto que podemos agregar más pulsadores, pero ya me conocen "mientras más difícil más se aprende".
En cuanto al código es el mismo que ya vimos. Solo se agrega el manejo de los pulsadores con el control de "debounce" que si no conoces el tema y quieres verlo en particular puedes ir a: Rebote de Pulsadores o Debounce
Vamos utilizar millis() también para representar los datos en pantalla dándonos un tiempo muy importante entre cuando se muestra la frecuencia y cuando se muestra el Duty Cycle.
En la imagen anterior podemos ver las conexiones, ¡presten atención al voltaje!
Podemos utilizar los pines Digitales desde el 2 al 12 (D2 a D12).
Este proyecto está dividido en dos partes:
Generador de Onda Cuadrada Parte 1 - Mostrando datos por el Display
Generador de Onda Cuadrada Parte 2 - Pulsadores y Test Final
En la Parte 1 vemos como remediar el inconveniente del Display que solo puede mostrar 4 dígitos, aprovechando que trabajamos con frecuencia y podemos derivar Hertz a Kilo Hertz.
En la Parte 2 vemos como aumentamos los valores de la Frecuencia y el Duty Cycle con los pulsadores.
El Código:
#include <TimerOne.h>
#include <Wire.h>
#include <DIYables_4Digit7Segment_74HC595.h>
#define SCLK_PIN 7 // Serial clock (SH_CP)
#define RCLK_PIN 6 // Register clock / latch (ST_CP)
#define DIO_PIN 5 // Data (DS)
#define BUTTON_PIN_F_UP 3
#define BUTTON_PIN_F_DOWN 8
#define BUTTON_PIN_D_UP 9
#define BUTTON_PIN_D_DOWN 11
DIYables_4Digit7Segment_74HC595 display(SCLK_PIN, RCLK_PIN, DIO_PIN);
byte lastButtonStateFUp = LOW;
byte lastButtonStateFDown = LOW;
byte lastButtonStateDUp = LOW;
byte lastButtonStateDDown = LOW;
unsigned long debounceDuration = 50; // millis
unsigned long durationmsg = 2000; // millis
unsigned long lastTimeButtonStateChanged = 0;
unsigned long lastTimeDuty = 0;
unsigned long f , t=4000 , k=512;
bool dutyShow = false;
byte k1;
void setup()
{
Serial.begin(115200);
display.begin();
// Arduino UNO solo pines 9 y 10 pueden utulizarse
pinMode(10, OUTPUT);
pinMode(BUTTON_PIN_F_UP, INPUT);
pinMode(BUTTON_PIN_F_DOWN, INPUT);
pinMode(BUTTON_PIN_D_UP, INPUT);
pinMode(BUTTON_PIN_D_DOWN, INPUT);
}
void loop()
{
display.loop();
Timer1.initialize(t);
Timer1.pwm(10, k);
f=1000000/t; // calculamos la frecuencia
k1=k*100/1024; // calculamos % el Duty Cicle
//Serial.print("Frecuencia:");
//Serial.println(f);
//Serial.print("Duty-Cycle:");
//Serial.println(k1);
if(dutyShow == false)
{
if(f <= 9999)
{
String F;
F += f;
char charBuf[9];
F.toCharArray(charBuf, 9);
display.print(charBuf);
}
if(f > 9999)
{
float frq = f;
float c;
c = frq/1000;
String F;
F += c;
char charBuf[9];
F.toCharArray(charBuf, 9);
display.print(charBuf);
}
lastTimeDuty = millis();
}
if(dutyShow == true){
String D = "d";
D += k1;
char charBuf[9];
D.toCharArray(charBuf, 9);
display.clear();
display.print(charBuf);
if(millis() - lastTimeDuty > durationmsg){dutyShow = false;}
}
if (millis() - lastTimeButtonStateChanged > debounceDuration) {
byte buttonState = digitalRead(BUTTON_PIN_F_UP);
if (buttonState != lastButtonStateFUp) {
lastTimeButtonStateChanged = millis();
lastButtonStateFUp = buttonState;
if (buttonState == LOW) {
if(t > 1000 && t <= 4000 )
{
t = t-1000;
}
if(t <= 1000 && t > 100 )
{
t = t-100;
}
if(t <= 100 && t > 10 )
{
t = t-10;
}
if(t <= 10 && t > 2 )
{
t = t-1;
}
}
}
}
if (millis() - lastTimeButtonStateChanged > debounceDuration) {
byte buttonState = digitalRead(BUTTON_PIN_F_DOWN);
if (buttonState != lastButtonStateFDown) {
lastTimeButtonStateChanged = millis();
lastButtonStateFDown = buttonState;
if (buttonState == LOW) {
if(t >= 1000 && t < 4000 )
{
t = t+1000;
}
if(t <= 900 && t >= 100 )
{
t = t+100;
}
if(t <= 100 && t >= 10 )
{
t = t+10;
}
if(t <= 10 && t >= 2 )
{
t = t+1;
}
}
}
}
if (millis() - lastTimeButtonStateChanged > debounceDuration) {
byte buttonState = digitalRead(BUTTON_PIN_D_UP);
if (buttonState != lastButtonStateDUp) {
lastTimeButtonStateChanged = millis();
lastButtonStateDUp = buttonState;
if (buttonState == LOW) {
if(k >= 256 && k < 1023)
{
k = k + 256;
dutyShow = true;
}
}
}
}
if (millis() - lastTimeButtonStateChanged > debounceDuration) {
byte buttonState = digitalRead(BUTTON_PIN_D_DOWN);
if (buttonState != lastButtonStateDDown) {
lastTimeButtonStateChanged = millis();
lastButtonStateDDown = buttonState;
if (buttonState == LOW) {
if(k > 256 && k <= 1024)
{
k = k - 256;
dutyShow = true;
}
}
}
}
}