9 clock signals in Arduino UNO/Mega

Hello everyone, I am trying to program 9 clock signals in Arduino UNO or Arduino Mega:

  • Each clock signal depends on two Inputs (one general switch and one particular switch for each signal).

  • I need that the frequencies could change from 5k to 8 kHz with a potentiometer.

  • I need that the program last for many hours.

So, the problems begin here:

  • I was using delays to keep the High or Low state for each signal, but I realized too late that the delay function is not recommendable because stops all the process of the Arduino which affects every frequency, duty cycle and period of the signals.
  • Then I decided to use the function micros in different loop functions (called by the main void loop) to avoid the problem that I mentioned above. The program with only 9 different signals worked well, but when I added the conditionals for each clock, the Low and High times were affected, giving a wrong frequency again (For a theorical 5 kHz, the oscilloscope shows a 2.6 kHz frequency approximately).
  • I suppose that the time in which the conditionals instructions are read is too long that affects my High and Low times. Is there any other option to reduce these times? or the problem is something completely different? In addition, I haven't put the code for the analog read from the potentiometer, and I supposse that this will affect the signals too.
  • I'm using an unsigned long variable to save the value of the micros() function, but this only can reach to the 4,294,967,295 number right? which means 4,294,967,295 microseconds or 1.19 hours. How can I avoid this time restriction? The added code will affect the times of the signals too, right?
    Thank you everyone in advance.

This is my program:

int Delay_uSec=80;
int ON=1;
int OFF=0;

//______________Uno____________________
//                  IDE                 Physical Pin
int General_Enable=19; // Digital19, An5        28

int Input1=0; // Digital0                  2
int Input2=1; // Digital1                  3
int Input3=2; // Digital2                  4
int Input4=3; // Digital3                  5
int Input5=4; // Digital4                  6

int Input6=5; // Digital5                  11
int Input7=6; // Digital6                  12
int Input8=7; // Digital7                  13
int Input9=8; // Digital8                  14

int Output1=18; // Digital18, Analog4        27
int Output2=17; // Digital17, Analog3        26
int Output3=16; // Digital16, Analog2        25
int Output4=15; // Digital15, Analog1        24
int Output5=14; // Digital14, Analog0        23

int Output6=12; // Digital12                 18
int Output7=11; // Digital11                 17
int Output8=10; // Digital10                 16
int Output9=9;  // Digital9                  15


void setup() 
{
  pinMode(General_Enable,INPUT);
  
  pinMode(Input1,INPUT);
  pinMode(Input2,INPUT);
  pinMode(Input3,INPUT);
  pinMode(Input4,INPUT);
  pinMode(Input5,INPUT);
  pinMode(Input6,INPUT);
  pinMode(Input7,INPUT);
  pinMode(Input8,INPUT);
  pinMode(Input9,INPUT);
  pinMode(Output1,OUTPUT);
  pinMode(Output2,OUTPUT);
  pinMode(Output3,OUTPUT);
  pinMode(Output4,OUTPUT);
  pinMode(Output5,OUTPUT);
  pinMode(Output6,OUTPUT);
  pinMode(Output7,OUTPUT);
  pinMode(Output8,OUTPUT);
  pinMode(Output9,OUTPUT);
}

void loop() 
{
    loop_1();
    loop_2();
    loop_3();
    loop_4();
    loop_5();
    loop_6();
    loop_7();
    loop_8();
    loop_9();
}

void loop_1()
{
  static unsigned long t_last_change=0; //64 bits, 4,294,967,295 us, 1.19 hours
  static int HLState=OFF;
  unsigned long t_now=micros();
  
  if ( (digitalRead(General_Enable)==HIGH) && (digitalRead(Input1)==LOW)&& (t_now-t_last_change > Delay_uSec))
    {
      t_last_change=t_now;
      HLState=!HLState;
      digitalWrite(Output1,HLState);
    }
}

// ...

void loop_9()
{   
  static unsigned long t_last_change=0;
  static int HLState=OFF;
  unsigned long t_now=micros();
  if ( (digitalRead(General_Enable)==HIGH) && (digitalRead(Input9)==LOW)&& (t_now-t_last_change > Delay_uSec))
    {
      t_last_change=t_now;
      HLState=!HLState;
      digitalWrite(Output9,HLState);
    }
}

Hello cd_2022
You may start and study the BLINKWITOUTDELAY example of the IDE to gain the knowledge how to built an Arduino timer.

Have a nice day and enjoy coding in C++.
Дайте миру шанс!

Do you mean that each clock will be individually adjustable (9 potentiometers), or will they all have the same frequency (1 potentiometer)?

Your code seems to allow for only 1 frequency, controlled by Delay_uSec, but has a t_last_change variable for each of the 9 clocks. This means the clocks will be almost but not exactly in phase with each other.

Are the phases of the 9 clocks important at all, assuming they are all the same frequency?

Using 9 loopN() functions, each with its own t_last_change variable is inefficient, if all the clocks are the same frequency, and phase does not matter. This could be why you can't achieve the higher frequencies.

A much, much easier way to achieve this could be to use the tone() function. However, only one pin can be used with tone() at any time. A couple of external chips like 74hc595 could be used to create up to 16 clocks that have same frequency & phase but are individually controllable on/off.

1 Like

Should should not use pins 0 & 1 on Uno/Mega. They are used for uploading your sketch and the serial monitor. Attaching external circuits to them can cause malfunctions or even damage if you do not know exactly what you are doing.

Also, your sketch is 9 times longer than it should be. You should learn to use arrays.

consider if not interested in high precision ( < 80 usec),

unsigned long PeriodUsec = 80;
#define M               1000
#define Tics(kHz)       int(kHz * M / (2*PeriodUsec))

#undef MyHW
#ifdef MyHW
const byte EnAllPin   = A0;
const byte EnPins  [] = { A1, A2, A3 };
const byte OutPins [] = { 10, 11, 12 };
const int  TglTics [] = { Tics(5.0), Tics(5.5), Tics(8.0) };

#else
const byte EnAllPin   = 19;
const byte EnPins  [] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
const byte OutPins [] = { 18, 17, 16, 15, 14, 12, 11, 10 , 9 };
const int  TglTics [] = {
    Tics(5.0), Tics(5.5), Tics(6.0),
    Tics(5.0), Tics(5.5), Tics(7.0),
    Tics(5.0), Tics(5.5), Tics(8.0)
};
#endif

#define N_PIN  sizeof (OutPins)

int      tics [N_PIN] = { 10, 11, 12 };
unsigned long usecLst;

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void loop ()
{
    if (HIGH == digitalRead (EnAllPin))
        return;

    unsigned long usec = micros ();
    if ((usec - usecLst) >= PeriodUsec)  {
        usecLst = usec;

        for (unsigned n = 0; n < N_PIN; n++)  {
            if (digitalRead (EnPins [n]))  {
                if (--tics [n] < 0)  {
                    tics [n] = TglTics [n];
                    digitalWrite (OutPins [n], ! digitalRead (OutPins [n]));
                }
            }
            else
                digitalWrite (OutPins [n], Off);
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    pinMode (EnAllPin,INPUT);

    for (unsigned n = 0; n < N_PIN; n++)  {
        pinMode (EnPins [n],  INPUT_PULLUP);
        pinMode (OutPins [n], OUTPUT);
    }
}

Bear in mind that only using analogRead, this is going to be very difficult, if not impossible, because the maximum rate you can read an analogue input is just less than 9kHz.
(A workaround is possible)

You could read enable pin once before all loops instead of in each loop. That would save you 8 digitalRead.

You can read multiple inputs in one go by reading the register. That saves a lot of time, but your code will only work on one specific type of controller then.
Same with digitalwrite...
If you want to go this way you should not make an array but store your output and states in a byte (since you have 9, you will need two bytes or one int).

You can save the state HLstate in a bool or byte (an int is two bytes and a 8 bit processor will need multiple clock cycles to process an int (compiler may be able to take shortcuts here).

Thanks, but that code is very similar for the one that I post.

  • I need only 1 potentiometer to change the general Delay_uSec.
  • It's not necessary that all the signals to be in phase, but I need that all have the same frequency.
  • I rewrote the code to eliminate the redundant lines but the signals are still wrong in frequency.
  • I was hoping to use only the Atmega328p and no other extra electronics stuff.

The reduced program:

{
  static unsigned long t_last_change=0; //64 bits, 4,294,967,295 us, 1.19 hours
  static int HLState=OFF;
  unsigned long t_now=micros();
  
  if ( (digitalRead(General_Enable)==HIGH) && (t_now-t_last_change > Delay_uSec))
    {
      t_last_change=t_now;
      HLState=!HLState;
    }
    
  if (digitalRead(Input1)==LOW)
  {digitalWrite(Output1,HLState);}

//...

  if (digitalRead(Input9)==LOW)
  {digitalWrite(Output9,HLState);}

}

With this program, the frequencies are still wrong.

I don't get what you're saying, I can't read the analogue input having the 9 signals?

You are running into issues with the speed of digitalWrite() and digitalRead().

Take a look at this library which is available through the library manager.
https://pololu.github.io/fastgpio-arduino/

The issue with analogRead() is that it takes about 100 microseconds. As said, it can be speeded up, but having it in your program will slow things down.

I think I understand what changes you made, but no-one else can test or improve on your code if you don't post the whole code.

Did you read my post #4?

You could avoid using pins 0 & 1 if you can save a few pins. One way to do that would be to connect your switches in a 3x3 matrix. If they are SPDT switches, you will need to wire a small diode (e.g 1N4148) in series with each switch. Using a matrix will save you 3 pins, so no need to use pins 0 & 1.

How important is it that your clock signals are steady, with low or no jitter? If that's important, you may need to use a hardware timer to control the output pins. That way, the code to create the clock signals can continue in the background independent of the the rest of your code which is reading analog inputs, scanning a key matrix etc.

Using hardware timers and fast gpio libraries will make your code less portable to other types of Arduino. Even moving from Uno to Mega may require some adjustments. This could be the price you have to pay to achieve your project.

https://www.pjrc.com/teensy/td_libs_TimerOne.html

I don't understand that phrase.
An analogRead takes well over 100 microseconds, and is blocking .

Hello, I'm trying to understand your code. I don't get what you're doing with these:

#define Tics(kHz)       int(kHz * M / (2*PeriodUsec)) 
const int  TglTics [] = {
    Tics(5.0), Tics(5.5), Tics(6.0),
    Tics(5.0), Tics(5.5), Tics(7.0),
    Tics(5.0), Tics(5.5), Tics(8.0)}; 
int      tics [N_PIN] = { 10, 11, 12 };

and with the operations that use them. Thank you in advance.

the timer condition is met every PeriodUsec (80). each output has a tic counter which decremented to zero toggles the output. the Tics() macro determines the # of tics -- timer events -- for toggling each output.

5kHz has a period of 200 usec and toggled every 100usec. 100 usec / PeriodUsec is 1. so with this crude timing, that output would be toggled every timer expiration or every 80 usec (which isn't very accurate). 8kHz requires 62.5 usec

you could have separate timers for each output, but then how much processing can be performed while achieving some minimal loop timing.

knowing the exact frequencies of each output may determine a particular value that works for all outputs

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.