-10 to 10V 3x CV Generator--Part III

Readers: If you'd like to build the CV Generator featured in this post, please go to PCBWAY's Community pages--gerber file; KiCAD 9 project/pcb/schematic/library files, STL for the OLED standoff, Arduino .INO file, B.O.M., and more, are here.  

You can also help out this blog immensely by checking out PCBWAY using the link here. Thanks!

==============

Howdy--this post is part III of the posts here and here

From last time--I created a simple control voltage generator, sourcing about 30mA with 3 independent pots, each output is -10 to +10V DC controlled by a dedicated pot.

For revision 3 I added a MCP4728 DAC and tweaked the 16-bit ADC analogReadEnhanced algo, using 3 of the 4728's 4 channels, to make the CV generator's output better match the values shown in the OLED.



This simple revision had a big problem....unlike REV 1 and 2 I couldn't get the I2C bus to work--no I2C signal, anywhere.  

I figured it was a hardware issue and spent a lot of time troubleshooting based on that assumption--nope.

Then I had to move my shop--pack up every tool, every part, every drawer, move it across town, then set it all back up again--fun stuff.

Once unpacked I revisited the build, and right away Claude.ai caught the problem; the sketch used the "SSD1306AsciiWire.h" library to drive the OLED and my code was written like this:

  Wire.begin(); //set I2C 

  oled.begin(&Adafruit128x64, I2C_ADDRESS); 

  Wire.setClock(400000L);  //set I2C speed. 


meaning the I2C speed was set after constructing the oled object.

Claude told me I had to add the Wire.setClock statement before calling oled.begin. I knew that?

So--should be this.

   Wire.begin(); //set I2C 

  Wire.setClock(400000L);  //set I2C speed. 

  oled.begin(&Adafruit128x64, I2C_ADDRESS); 


(The errant code worked in revision 1 and 2 but not this one, making this one hard to track down....)

Fixed!  

Otherwise a simple build:

As usual I used SMD SOIC and 1206 parts and a cheap hotplate.



Uploading firmware....




The OLED needed a standoff and I was surprised I couldn't find anything close to what I needed online so I designed and 3D printed one myself:


That sat between the front panel/Oled and the PCB:

 
 

I then packaged all of this up (gerber, Kicad files, STL file for the standoff, Arduino code, and all the rest)--and put it on PCBWAY's community site , go here. Speaking of Serene and the good folks at PCBWAY, here's a word from this blog's humble sponsor:


      

 For all your prototype and production needs, PCBWAY is the place to go.

they can fabricate PCBs using full color! Details here

In addition to top shelf PCB fabrication they also do fantastic work with assembly3D printinginjection molding, and much more. 

Their staff is extremely helpful and PCBWAY always turns work around quickly. 

As always--you can help this blog by checking out the PCBWAY site. Thanks!

Back to it.....


Boring AI background. 

Wait, is that it?  Yep that's it for now. 

Gotta keep moving.

See ya next time.

Arduino HID Pushbuttons in a Harmonica Case; New Workshop (and) Moving Sucks

Over the past several weeks I had to pack up my entire bench, all parts, all tools, and everything else I own, and move them to a rental place across town while my psychiatrist wife's house gets a new foundation. 

Moving, especially when you are older and fully set in your ways, is stressful and generally not recommended.

We've moved in. Sort of.

To shake out my temporary workshop I built a simple USB-HID keyboard using perf, Arduino IDE, Claude Code, a DS3231 RTC (realtime clock) breakout board and an RP2040 MCU--specifically the Seeed XIAO-RP2040.  


Press a button, the current date and time are displayed on whatever PC you have the widget plugged into.  Press the other 2 and 2 pre-programmed strings appear.

Worked first time, here's what the finished thingy looks like--I may put labels and dumb decals on it, but good enough for now.

Hohner harp cases -- great for small projects.


PCBLESS

I hadn't built anything with perf in a long time, it was--fun?

Parts are mostly from the junk box....


The jury is still out.

The DS3231 I2C RTC breakout board is a no-name-o from Amazon. Without it, the RP2040 loses track of internal time during a power outage.

If you want this BoB check the link here, but the link may break after a few months....whatever; this is a common BoB and is probably available from your favorite tariff-ingesting retailer; match the photo:










Wiring: the upper IC is the SEEED MCU, bottom 4 pins (SCL, SDA, Vcc, GND) are the wirepads of the RTC breakout board.  Two views--from the top and bottom--are in one drawing. I have found with perf projects, if I don't create a guide like this, very likely I will make wiring mistakes....



THE CODE

....centers on the incredible C/C++ RP2040 SDK to Arduino port from Earle Philhower III, check out the extensive documentation here, Swiss accent guy's video here

Wow, Earle worked super hard so we don't have to--we owe Earle our gratitude!

Being an employable up-to-date tech (also intellectually disinterested?) I used Claude Code to generate sketches for the project.

Claude code is amazing and terrifying....the damn thing got most of what is below right in one pass, making a few stupid (but major) mistakes--whatever, easily fixed.....quicker to get things close with Claude and fix bugs by hand vs. code by hand and fix bugs by hand.

This first sketch sets the RTC to current time. Modify the rtc.adjust line, compile, and upload.



#include 
#include 

RTC_DS3231 rtc;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (!rtc.begin()) {
    Serial.println("DS3231 not found!");
    while (true);
  }

  // >>> SET YOUR CURRENT TIME HERE <<<
  // Format: DateTime(YYYY, MM, DD, HH, MM, SS)  — 24-hour time
  rtc.adjust(DateTime(2026, 3, 15, 18, 50, 0));

  Serial.println("RTC time has been set!");

  // Verify by reading it back
  DateTime now = rtc.now();
  Serial.print("Time is now: ");
  Serial.print(now.month());   Serial.print("/");
  Serial.print(now.day());     Serial.print("/");
  Serial.print(now.year());    Serial.print(" ");
  Serial.print(now.hour());    Serial.print(":");
  if (now.minute() < 10) Serial.print("0");
  Serial.print(now.minute());  Serial.print(":");
  if (now.second() < 10) Serial.print("0");
  Serial.println(now.second());
}

void loop() {
  // Nothing — time is set, job done.
}


The second sketch makes pushing the buttons display current date/time as well as a couple of strings--replace string1 and string2 values with your own:




#include 
#include 
#include 

/* Board manager:
   https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
   Board: Seeed XIAO RP2040
   USB Stack: PICO SDK

   Library required: RTClib by Adafruit (Library Manager)

   DS3231 Wiring:
     VCC -> 3.3V
     GND -> GND
     SDA -> D4 (GPIO 6)
     SCL -> D5 (GPIO 7)

   NOTE: Button 3 (Email 2) moved from D4 to D6 to free up I2C SDA.
*/

RTC_DS3231 rtc;

// Button pins
const int buttonD1 = D1; // Date/Time
const int buttonD3 = D3; // Email 1
const int buttonD6 = D6; // Email 2 — moved from D4 to avoid I2C conflict

// Button states
bool lastStateD1 = HIGH;
bool lastStateD3 = HIGH;
bool lastStateD6 = HIGH;

void setup() {
  pinMode(buttonD1, INPUT_PULLUP);
  pinMode(buttonD3, INPUT_PULLUP);
  pinMode(buttonD6, INPUT_PULLUP);

  Keyboard.begin();
  Wire.begin();

  if (!rtc.begin()) {
    // RTC not found — flash onboard LED as error indicator
    pinMode(LED_BUILTIN, OUTPUT);
    while (true) {
      digitalWrite(LED_BUILTIN, HIGH); delay(200);
      digitalWrite(LED_BUILTIN, LOW);  delay(200);
    }
  }

  // If the RTC lost power (dead/missing battery), set it to compile time.
  // Once the coin cell is installed this will not overwrite a valid time.
 // if (rtc.lostPower()) {
  //  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
 // }
}

void loop() {
  bool currentStateD1 = digitalRead(buttonD1);
  bool currentStateD3 = digitalRead(buttonD3);
  bool currentStateD6 = digitalRead(buttonD6);

  if (currentStateD1 == LOW && lastStateD1 == HIGH) {
    delay(50);
    printRTCTime();
    delay(300);
  }

  if (currentStateD3 == LOW && lastStateD3 == HIGH) {
    delay(50);
    Keyboard.print("string1");
    delay(300);
  }

  if (currentStateD6 == LOW && lastStateD6 == HIGH) {
    delay(50);
    Keyboard.print("string2");
    delay(300);
  }

  lastStateD1 = currentStateD1;
  lastStateD3 = currentStateD3;
  lastStateD6 = currentStateD6;
}

void printRTCTime() {
  DateTime now = rtc.now();

  Keyboard.print(now.month());
  Keyboard.print("/");
  Keyboard.print(now.day());
  Keyboard.print("/");
  Keyboard.print(now.year());
  Keyboard.print(" ");

  if (now.hour() < 10) Keyboard.print("0");
  Keyboard.print(now.hour());
  Keyboard.print(":");
  if (now.minute() < 10) Keyboard.print("0");
  Keyboard.print(now.minute());
  Keyboard.print(":");
  if (now.second() < 10) Keyboard.print("0");
  Keyboard.print(now.second());
}

THE PLUG 


I will need a PCB for the next one of these I build, and maybe a more durable case.  For this, this blog's sponsor, PCBWAY, can fabricate the PCB's, the case, and everything else.
In addition to top shelf PCB fabrication they also do fantastic work with assembly, 3D printing, injection molding, and much more.  
As always--you can help this blog by checking out the PCBWAY site.  Thank you!

BENCHED MACH II

Here's the new bench setup. 

We are getting moved in and simple things like getting cold water from the kitchen sink is driving us nuts.  

I am told the foundation work, including lifting the house, redoing the French drains, fixing the roof line, bringing electrical up to code, and everything else needed in Earthquake Country, will take a year to a year and a half.

So we are here. For now.

What you see below works--sorta.



Good enough to perf....

See you next time.