Sanity Check on Square Wave Generation Code

Hello Everybody,

I am trying to code the simplest square wave possible with an ATtiny85. My code is intended to give some frequency change to the waveform by reading a potentiometer position, in a CONTINUOUS way. What I have is instead a STEPPED (10 steps circa) control over the waveform.
Here is my code:
(please notice that I am not using "Tone()" because it's also a learning experience)

const int potPin = 3;  // Input to set waveform period delta
const int outPin = 4;  // wave out

unsigned long lastUpdate = 0;
unsigned int sampleInterval = 0;   // waveform (half) period
byte waveValue = 0;        // instant wave value
int potVal;

void setup() {
  pinMode(outPin, OUTPUT);
}

void loop() {
  potVal = analogRead(potPin);
  sampleInterval = 2080 + potVal; //we toy with (half) period directly
  
  if (micros() - lastUpdate >= sampleInterval) {
    lastUpdate = micros();
    analogWrite(outPin, waveValue); // send the new wave value
    if(waveValue == 0){
      waveValue = 255;
    }
    else{
      waveValue = 0;
    }
  }
}

At first I tought it cold be something related to the bit resolution of ATtiny85, but it's 10-bit, so the pot reading should be a continuous 0-1023 value...

Thanks in advance for the help!

The value returned by micros() increments in steps of 4 rather than 1. Could this be contributing to the problem ?

Try printing the value to see whether this is true

why do you use analogWrite when digitalWrite with HIGH and LOW would do ?

you could just flip the pin

digitalWrite(outPin, digitalRead(outPin) == HIGH ? LOW : HIGH); // flip the slow way
1 Like

Is ATtiny85 (Spence Konde core) compatible with Serial?

That 4 steps increment is a nice clue, yes. It could in part explain what I am experiencing, yes

You are right. I m using analog because I started with the idea of using a SAW wave, but rolled back to square. Thanks for the hint!

the ATtiny85 does not have a DAC, I think it has three PWM-capable pins: 0, 1 and 4.

analogWrite() would just do PWM (ie fast switching between HIGH and LOW based on a duty cycle.)

Remember that analogRead() will take ~100µs so that gives the pace for your loop

I made some calc and, no, the micros() 4-steps increment is not the core cause of the stepped behaviour I see.

It is more like the potentiometer gives the whole 0-1023 range span, but in increments of 100 instead of 1 (or 4...)

Any other idea?

(thanks!!)

what kind of pot did you connect ? (Resistance ?)

How do you know this?
Did you use a scope?

Hi, @Barito

Can you please post a copy of your circuit, a picture of a hand drawn circuit in jpg, png?
Hand drawn and photographed is perfectly acceptable.
Please include ALL hardware, power supplies, component names and pin labels.

Tom.... :smiley: :+1: :coffee: :australia:

Yes. But you do have to use pins specified in the documentation. And you will need a USB-Serial adapter, of course.

I suspect it may be something related to using analogWrite(), so try it with digitalWrite().

Thank you all for your answers, very much appreciated.

The circuit is as simple as it could be:

  • a 10K trimmer with wiper connected to ATtiny pb3, the other two pins to gnd and +5V
  • ATtiny pb4 goes to an RC filter (270 ohm in series and 100nF cap to gnd)
  • the signal out of the filter is monitored both with an oscilloscope and earphones.
  • ATtiny is 5V powered

I am not using analogWrite but digitalWrite with the exact line Jackson have been suggested. Still a stepped behaviour I see/ear

How did you see?

With the oscilloscope

I've done some tests to verify or disprove Barito's findings.

I've got some ATtiny85s somewhere, but I couldn't find them, so I used an Arduino Uno R3 instead.

I used Barito's code, modified to use pins that were convenient to me, and used digitalWrite() instead of analogWrite().

Code Used
const int potPin = A0;  // Input to set waveform period delta
const int outPin = 12;  // wave out

unsigned long lastUpdate = 0;
unsigned int sampleInterval = 0;   // waveform (half) period
byte waveValue = 0;        // instant wave value
int potVal;

void setup() {
  pinMode(outPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  potVal = analogRead(potPin);
  sampleInterval = 2080 + potVal; //we toy with (half) period directly
  
  if (micros() - lastUpdate >= sampleInterval) {
    lastUpdate = micros();
    digitalWrite(outPin, waveValue); // send the new wave value
    if (waveValue == 0) {
      waveValue = 1;
    }
    else {
      waveValue = 0;
    }
  }
}

Rather than using a potentiometer, I used a function generator to apply a slowly ramping voltage to analogue input A0, at a frequency of 1mHz.

I monitored the ramp voltage and the pulse generated on an oscilloscope:

I've got two of the oscilloscope's automatic measurements turned on - the mean value of the input voltage, and the period of the square wave.

I programmed an Arduino MKR WiFi 1010 with Ethernet Shield to read the measurements off the oscilloscope at one second intervals, and display them on the Serial Monitor.

MKR WiFi 1010 Code
#include <SPI.h>
#include <Ethernet.h>

unsigned long previousMillis = 0;
const long interval = 1000;
int outPin = 9;
byte outVal = 0;

// Enter a MAC address and IP address for your Arduino
byte mac[] = { 0xA8, 0x61, 0x0A, 0xAE, 0x0B, 0xB5 };
IPAddress ip(192, 168, 0, 10);                          // desired Arduino IP address
IPAddress server(192, 168, 0, 3);                       // IP address of the Oscilloscope

EthernetClient client;

void setup() {

  // Start the Ethernet connection:
  Ethernet.begin(mac, ip);
  // Open serial communications and wait for port to open:
  Serial.begin(9600);


  // Give the Ethernet shield a second to initialize + additional time to open Serial Monitor:
  delay(10000);
  Serial.println("connecting...");

  // Connect to the DMM:
  if (client.connect(server, 5025)) { // Port 5025 is commonly used for SCPI
    Serial.println("connected");
    // Send an SCPI command to the instrument:
    client.println("*IDN?");
  } else if (client.connect(server, 5555)) { // Port 5555 is used for SCPI by Rigol
    Serial.println("connected");
    // Send an SCPI command to the instrument:
    client.println("*IDN?");
  }  else {
    // If you didn't get a connection to the server:
    Serial.println("connection failed");
  }
  // delay(1000);
  while (client.available()) {
    char c = client.read();
    Serial.print(c);
    delay(100);
  }
}

void loop() {

  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    
    client.println("PAVA? CUSTALL");    // SCPI command to read all measurements

    // If there are incoming bytes available from the Oscilloscope:
    while (client.available()) {
      char c = client.read();
      Serial.print(c);
    }
    
    // If the server disconnected, stop the client:
    if (!client.connected()) {
      Serial.println();
      Serial.println("disconnecting.");
      client.stop();
      // Do nothing more:
      while (true);
    }
  }
}

Here is what the results on the Serial Monitor look like:


I've arrowed the columns with the mean voltage and period measurements in.

I left the test to run for one whole cycle of the 1mHz swept voltage (1000 seconds).

I copied the results from the serial Monitor into a .CSV file, and opened them in Excel.
Using the Excel TEXTBEFORE( ) function I was able to extract the numerical value of the measurements taken.
I converted the mean voltage into milliVolts, and the square wave period into microseconds so they could be displayed on the same graph.

The results were as follows:

Exactly as Barito had described.

1 Like

I forgot to mention, that if you print out the values of 'potVal' and 'sampleInterval' on the Serial Plotter, then the values of 'sampleInterval' are calculated correctly:

5Hz ramp waveform.

what happens if you test this

const int potPin = 3;  // Input to set waveform period delta
const int outPin = 4;  // wave out
unsigned long sampleInterval = 2080ul;
unsigned long lastUpdate = -sampleInterval; // to trigger right away

void setup() {
  pinMode(outPin, OUTPUT);
}

void loop() {
  if (micros() - lastUpdate >= sampleInterval) {
    digitalWrite(outPin, digitalRead(outPin) == HIGH ? LOW : HIGH); // flip the slow way
    sampleInterval = 2080ul + analogRead(potPin);;
    lastUpdate = micros();
  }
}

ie we only read the pot when we flip the output (which should be frequent enough compared to human time required to move the slider of the pot)

I'll test it and report back.

It's looking better already.

I'll let the test run for the full 1000s and graph the results, so we are comparing like for like.

what square wave frequencies are you looking for?
if I run the following on a ATtiny85

// ATtiny85 read ADC1 for period of square wave

#include <SoftwareSerial.h>

#define RX 3       // *** P3 to FTDI Tx
#define TX 4       // *** P4 to FTDI Rx
#define ADC1 1     // ADC1 is on P2
#define SQ_WAVE 1  // output on ATtiny85 P1

int counter;
SoftwareSerial MySerial(RX, TX);

// *** Pinout ATtiny85:
// P0  (MOSI/DI/SDA/AIN0/OC0A/OC1A/AREF/PCINT0)
// P1  (MISO/DO/AIN1/OC0B/OC1A/PCINT1)
// P2  (SCK/USCK/SCL/ADC1/T0/INT0/PCINT2)
// P3  (PCINT3/XTAL1/CLKI/OC1B/ADC3)
// P4  (PCINT4/XTAL2/CLKO/OC1B/ADC2)
// P5  (PCINT5/RESET/ADC0/dW)

void setup() {
  MySerial.begin(9600);
  pinMode(SQ_WAVE, OUTPUT);  // output pin
}

void loop() {
    unsigned long int period2 = analogRead(ADC1)*10UL;
    digitalWrite(SQ_WAVE, HIGH);                   // high output
    delayMicroseconds(period2);                    // delay for half period
    digitalWrite(SQ_WAVE, LOW);                    // low output
    delayMicroseconds(period2);                    // delay for half period
    //return;                                        // comment out to display period
    // this will upset LOW half period at high frequencies
    static long int timer1 = millis();
    if (millis() - timer1 > 1000) {
      timer1 = millis();
      MySerial.println(period2 * 2);
    }
}

I can get a frequency range if approximately 50Hz to 1.5kHz, e.g.