// Adafruit PWM servo board oscillator test
// https://wokwi.com/projects/412405035088333825
// Wokwi Custom Chip PSC9685
// from https://github.com/bonnyr/wokwi-pca9685-custom-chip
// with Adafruit PWM servo library from
// https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/blob/master/examples/oscillator/oscillator.ino
//
// See also:
// GPIO test https://wokwi.com/projects/412403579090783233
// Oscillator Test https://wokwi.com/projects/412405035088333825
// PWMtest https://wokwi.com/projects/412407132298758145
// Servotest https://wokwi.com/projects/412407755163620353
//
// For https://wokwi.com/projects/412407755163620353
//
// Uses https://github.com/bonnyr/wokwi-pca9685-custom-chip
// https://www.adafruit.com/product/815
// https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf
// Addresses 0x40+{A0:A5} -(avoid AllCall: 0xE0 & Reset 0x06)
// Custom chips playground
// See https://link.wokwi.com/custom-chips-alpha for more info
// ======================= Copied Definitions ====================
// Copied some adafruit servo driver code rather than import the library - will do that later
// This explains the defines below etc...
/***************************************************
This is an example for our Adafruit 16-channel PWM & Servo driver
to calibrate the frequency of the oscillator clock of the PCA9685.
CAUTION: DO NOT CONNECT ANY VOLTAGE HIGHER THAN THE BOARD LIMITS.
For 3.3V boards, like the ESP8266, remove any 5V input. The setup will
feed the voltage back into the board to measure the frequency.
KABOEM, SMOKE if you use too much VOLTAGE.
Connect the PCA9685 with I2C (Ground, VCC, SCL, SCA) and apply
voltage on V+. See above not higher than board limits.
Connect the signal (yellow pin, PWM) of the PCA9685 to your board:
Default is pin 3, last of first block.
Default is pin 14 (of your ESP8266).
Formula for prescale to get the targetted frequency (=update_rate) is:
prescale = round ( osc_clock / 4096 * update_rate) - 1
rewritten: osc_clock = (prescale + 1) * 4096 * update_rate
We will measure the real update_rate to assert the real osc_clock.
***************************************************/
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x5f, Wire);
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);
#if (defined(ESP8266) || defined(ESP32))
// Applied frequency in the test: can be changed to get the optimal
// oscillator calibration for your targetted frequency.
#define FREQUENCY 50
// CAUTION: ONLY CONNECT server and ESP WITHOUT 5V ON V+ or green breakout supply pins. Use 3.3V on V+
#define PIN_SERVO_FEEDBACK 3 // Connect Yellow PWM pin, 3 = last on first block
#define PIN_BOARD_FEEDBACK 14 // 14 => D5 on NodeMCU
uint8_t prescale = 0;
// loop
#define INTERVAL 1000 // 1 sec
int32_t lastEvaluation = 0;
uint16_t frozenCounter = 0;
uint16_t countDeviations = 0;
uint32_t totalCounter = 0;
uint32_t totalTime = 0; // in millis
uint32_t realOsciFreq = 0;
uint32_t multiplier = 4096;
// interrupt
volatile uint16_t interruptCounter = 0;
ICACHE_RAM_ATTR void handleInterrupt() {
interruptCounter = interruptCounter + 1;
}
void setup() {
Serial.begin(115200);
Serial.println("PCA9685 Oscillator test");
// set PCA9685
pwm.begin();
pwm.setPWMFreq(FREQUENCY); // Set some frequency
pwm.setPWM(PIN_SERVO_FEEDBACK,0,2048); // half of time high, half of time low
prescale = pwm.readPrescale(); // read prescale
Serial.printf("Target frequency: %u\n", FREQUENCY);
Serial.printf("Applied prescale: %u\n", prescale);
// prepare interrupt on ESP pin
pinMode(PIN_BOARD_FEEDBACK, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN_BOARD_FEEDBACK), handleInterrupt, RISING);
// take a breath and reset to zero
delay(10);
interruptCounter = 0;
lastEvaluation = millis();
}
void loop() {
if (millis() - lastEvaluation > INTERVAL)
{
// first freeze counters and adjust for new round
frozenCounter = interruptCounter; // first freeze counter
interruptCounter = interruptCounter - frozenCounter;
lastEvaluation += INTERVAL;
totalCounter += frozenCounter;
totalTime += 1;
// only print deviations from targetted frequency
//if (frozenCounter != FREQUENCY)
{
multiplier = 4096;
realOsciFreq = (prescale + 1) * totalCounter; // first part calcutlation
// now follows an ugly hack to have maximum precision in 32 bits
while (((realOsciFreq & 0x80000000) == 0) && (multiplier != 1))
{
realOsciFreq <<= 1;
multiplier >>= 1;
}
realOsciFreq /= totalTime;
if (multiplier) realOsciFreq *= multiplier;
countDeviations++;
Serial.printf("%4u", countDeviations);
Serial.printf(" Timestamp: %4" PRIu32 " ", totalTime);
Serial.printf(" Freq: %4u ", frozenCounter);
Serial.printf(" Counter: %6" PRIu32 " ", totalCounter);
Serial.printf(" calc.osci.freq: %9" PRIu32 "\n",realOsciFreq);
}
}
}
#else
void setup() {
Serial.begin(115200);
Serial.println("PCA9685 Oscillator test");
Serial.println("yet not available for your board."); // please help adapt the code!
}
void loop() {}
#endif // ESP8266/ESP32