Home » Projects » Electronics Projects » Arduino Signal Generator – Hardware and Software

Arduino Signal Generator – Hardware and Software

Arduino signal generator

 

The best part about building an Arduino-based project is that you can take a modular approach. As described earlier, my Arduino signal generator involves the integration of a number of modules: Arduino, LCD Keypad, optical encoder and digital signal generator. The most successful approach is usually to get each module working correctly, one function at a time.

In this article, I describe integrating the controls and display.  This involves understanding the hardware, and writing the software to make everything work together.

I am building an Arduino signal generator that requires the following control and display functions:

  • Display the frequency and other information on the LCD.
  • Change the frequency by either pressing the Up or Down buttons on the keypad, or turning the knob on the encoder.
  • Select the tuning steps, ranging from 1Hz to 1MHz by pressing the Left or Right buttons on the keypad.
  • Choosing whether to tune one step at a time (in which case I want the change to happen when the button is released) or sweep up or down (in which case the frequency changes as long as I hold in the button.

Sounds straightforward, right? There were a couple of challenges in integrating my Arduino signal generator. The first challenge is called “de-bouncing”. Believe it or not, when you turn a switch on or press a button, the signal change is not a perfect transition. There are transient pulses. The signal actually bounces for a few milliseconds before it settles down. This bouncing can cause errors in reading the signal. The typical solution is to attach a large value capacitor of 0.1 microfarad between the switch and ground. This acts as a sort of filter for the transient pulses. In the case of the KY-040 encoder, I attached such a capacitor from each of the signal lines to ground, before connecting the encoder to the Arduino. In the case of the keypad buttons, I read the signal twice over a few milliseconds and only accept the data if it is steady.

The second challenge is about how the Arduino signal generator monitors the hardware to detect changes such as a button push or knob turn. You can do this either by polling (asking the hardware for its status every few milliseconds) or using something called a “hardware interrupt”. For the keypad buttons, I use the polling approach. This means that the program running on the Arduino signal generator continuously asks the buttons about their status, and then responds according to which button was pushed. For the optical encoder, I used the “hardware interrupt”. This means that my program doesn’t worry about the knob being turned, because when you turn it, the Arduino hardware senses the change and interrupts the program with information about the change. In the case of the encoder, the interrupt service routines basically says “hey, the knob just turned upwards, do something!”.

One last thing. I switched from the smaller Arduino UNO to the larger Arduino MEGA board because it has more digital I/O pins available and I needed them. This will add a few more dollars to the parts cost.

Customized Code for Arduino Signal Generator

Most of the open source programming libraries for this hardware did not provide enough of the capabilities I wanted, or did not do a good enough job of things like controlling interrupts or fixing transients like de-bouncing. So, I took the best ideas from my thinking and research and wrote a small test program to do all of the functions listed above. Later, I will convert this code into a library. If you want to duplicate this project, the following code will get you started.

#include "Arduino.h"
#include <LiquidCrystal.h>

#define BUTTON_NONE   0
#define BUTTON_RIGHT  1
#define BUTTON_UP     2
#define BUTTON_DOWN   3
#define BUTTON_LEFT   4
#define BUTTON_SELECT 5
#define PIN_B    0
#define REPEAT_DELAY  5
/*
 KY-040 Encoder source code modified from 
        https://forum.arduino.cc/index.php?topic=242356.0
 KY-040 Encoder read switch press based on
        
 LCD 1620 Keypad source code modified from
        https://github.com/jraedler/LCDButtons
 */

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

unsigned long Frequency;      //for Synthesizer
unsigned long FrequencyStep;  //for Synthesizer - frequency increment
boolean Repeating;            //for Synthesizer - UP & DOWN buttons repeat when held, otherwise single

volatile boolean TurnDetected;  //KY040 isr - encoder is turning
volatile boolean Up;            //KY040 isr - turning direction

const int kyCLK = 20;  //KY040 pin CLK attached to MEGA pin 20 (Int 3) and 0.1uF bypass to ground
const int kyDT = 21;   //KY040 pin DT attached to MEGA pin 21 and 0.1uF bypass to ground
const int kySW = 31;   //KY040 pin SW attached to MEGA pin 31 and pulled up with 10K resister to V+

int StepIndex;
// Indexed arrays of frequency increment values and 
// display information, indexed by StepIndex
unsigned long StepValues[7] = {
  1, 10, 100, 1000, 10000, 100000, 1000000};
char* StepStrings[] = {
  "1H", "10H", "100H", "1K", "10K", "100K", "1M"};

/*
 Function to read a button push on LCD 1602 Keypad
 UP or DOWN to Change Frequency
 LEFT or RIGHT to Change StepIndex for Frequency Step
 SELECT to toggle between Repeating UP/DOWN or non-Repeating
 */
int ReadBtn(){
  static int vi[6] ={
    1020, 30, 150, 360, 535, 760                };
  byte i, j;
  int v;

  v = analogRead(PIN_B);
  if (v >= vi[0]) return BUTTON_NONE;
  while (1) {
    for (i = 1; i < 5; i++) if (v < vi[i]) break;
    delay(REPEAT_DELAY);
    v = analogRead(PIN_B);
    if (v >= vi[0]) return BUTTON_NONE;
    for (j = 1; j < 5; j++) if (v < vi[j]) break;
    if (i == j) break;
    i = j;
  }

  // If Repeating=true then send UP or DOWN continuously
  // Otherwise, wait for button to be released and then send a single press
  if (((i == BUTTON_UP) || (i == BUTTON_DOWN)) && (Repeating)) return i;
  while (1) {
    if (analogRead(PIN_B) >= vi[0]) return i;
    delay(REPEAT_DELAY);
  }
}

// Display current frequency on LCD line 1 (left side)
void ShowFrequency(unsigned long f){
  char buf[9] = "";
  sprintf(buf,"%8lu ", f);
  lcd.setCursor(0,0);
  lcd.print(buf);  
}

// Display frequency increment on LDCD line 1 (right side)
void ShowStep(int index){
  char buf[5] = "";
  sprintf(buf, "%4s", StepStrings[index]);
  lcd.setCursor(12,0);
  lcd.print(buf);  
}

// Indicate that UP and DOWN keys repeat for frequency change
void ShowRepeating(boolean b){
  lcd.setCursor(10,0);
  if (b){
    lcd.print("r");
  }
  else{
    lcd.print(" ");  
  }
}

// Interrupt service routine for reading KY-040 encoder action
void isr (){
  Up = (digitalRead(kyCLK) == digitalRead(kyDT));
  TurnDetected = true;
}

void setup(){
  lcd.begin(16, 2);
  lcd.clear();
  Frequency = 10000000;
  StepIndex = 0;
  FrequencyStep = StepValues[StepIndex]; 
  ShowFrequency(Frequency);
  ShowStep(StepIndex);
  Repeating = false;
  ShowRepeating(Repeating);

  pinMode(kyCLK,INPUT);
  pinMode(kyDT,INPUT); 
  attachInterrupt (3,isr,CHANGE);
  pinMode(kySW, INPUT);
}

void loop(){
  int btn;  // LCD 1602 Keypad button

  // Poll LCD 1602 Keypad buttons
  btn = ReadBtn();

  // Process LCD 1602 Keypad buttons
  switch (btn) {

  case BUTTON_LEFT:
    StepIndex--;
    if (StepIndex < 0)
      StepIndex = 0;
    ShowStep(StepIndex); 
    FrequencyStep = StepValues[StepIndex]; 
    break;

  case BUTTON_RIGHT:
    StepIndex++;
    if (StepIndex > 6)
      StepIndex = 6;
    ShowStep(StepIndex);
    FrequencyStep = StepValues[StepIndex];   
    break;

  case BUTTON_UP:
    Frequency += FrequencyStep;
    ShowFrequency(Frequency);
    break;

  case BUTTON_DOWN:
    Frequency -= FrequencyStep;
    ShowFrequency(Frequency);
    break;

  case BUTTON_SELECT:
    Repeating = !Repeating;
    ShowRepeating(Repeating);
    break;
  }

  // Process KY-040 encoder turns
  if (TurnDetected) {
    if (Up)
      Frequency -= FrequencyStep;
    else
      Frequency += FrequencyStep;
    TurnDetected = false; 
    ShowFrequency(Frequency);
  }

  // Process KY-040 encoder switch
  if (!(digitalRead(kySW))){
    while (!digitalRead(kySW)){
    } 
    delay(10);
    // For test purposes, just toggle Repeating, same as BUTTON_SELECT
    Repeating = !Repeating;
    ShowRepeating(Repeating);
  }
}

This is not “final code”, just a proof-of-concept. Together with the proof-of-concept experiments that I have done with the AD9850 direct digital synthesizer, I now have all of the basic software and hardware integration I need to move on to the final design of my Arduino signal generator.

2 comments

    • John VE6EY says:

      Hello. This signal generator is for radio. If you want an audio generator, that is easy to do with your PC sound card and some software.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.