I saw a project for a mechanical, 7-segment digit using a motor driven cam to lift the segments. As the motor rotated the cam, brass "lifters" would put segments in place to form the digit.
I liked it, but decided not to try a cam, and use SG90 servos, or at least to write a sketch for a single, 7-segment digit, driven by SG90 (seven per digit) to lift the segments.
Rather than using a typical bit-pattern to lower every segment, then lift every needed segment for every digit, I wanted to use a least-movement approach, using "kept" segments shared between adjacent digits, "removed" segments not used in the following digit and "added" segments not used in the previous digit. This is the highlight, and why I filed this under "programming."
I included a WS2812-digit to color-code the segments that that have been "kept", "removed" and "added".
An "ASCII-drawing" of the physical setup of the servo lifters is in the comments.
At the bottom of the sketch are notes for the next step; a four-digit clock.
// Mechanical, single-digit, seven-segment display.
// Each segment is attached to a servo by an arm
// See bottom of code for MUXED servos
#include <FastLED.h> // https://github.com/FastLED/FastLED
#define NUMPIX 21 // 3pix x 7seg
#define DATAPIN 10 //
#define MAXBRIGHT 255 // adjust
CRGB led[NUMPIX]; // create WS2812B object
#include <Servo.h> // https://github.com/arduino-libraries/Servo
#define SEGMENTS 7 // one servo per segment
Servo servo[SEGMENTS]; // seven servos
int armup = 0, armdown = 90; // servo horn positions
byte count; // case counter
unsigned long timer; // time digit change
enum segs { sega, segb, segc, segd, sege, segf, segg
}; // enumerated segments for calling their servo-pin number
byte srvpin[] = { 2, 8, 6, 5, 4, 3, 7 }; // pins for seg a, b, c, d, e, f, g
/*
SEGMENTS attached to SERVOS by non-interfering ARMS.
- START SEGMENTS (segments in current digit)
- KEEP SEGMENTS (will be used in next digit)
- REMOVE SEGMENTS (will not be used in next digit)
- ADD SEGMENTS (will be added to next digit)
. _____
+--------/--a \
| /\\_____//\
| | | | |
| +---|-f| |b-|-----+
| | | |_____| | |
| | \// g--\\/----+ |
| | /\\_____//\ | |
| | | | | | | |
| | +-|-e| |c-|-+ | |
| | | | |_____| | | | |
| | | \// d \\/ | | |
| | | \__ __/ | | |
| | | | | | |
+ + + + + + +
a f e d c g b <- SEGMENT SERVOS
WS2812B GRN - Kept segment (armup)
WS2812B RED - Removed segment (armdown)
WS2812B BLU - Added segment (armup)
. _____
/ a \ leda 0, 1, 2
/\\_____//\
| | | | ledb 3, 4, 5
| f| |b | ledf 6, 7, 8
| |_____| |
\// g \\/ ledg 9, 10, 11
/\\_____//\
| | | | ledc 12, 13, 14
| e| |c | lede 15, 16, 17
| |_____| |
\// d \\/ ledd 18, 19, 20
\_____/
*/
void setup() {
Serial.begin(115200);
FastLED.addLeds<WS2812B, DATAPIN, GRB>(led, NUMPIX);
FastLED.setBrightness(MAXBRIGHT);
FastLED.clear();
FastLED.show();
for (byte i = 0; i < SEGMENTS; i++) {
servo[i].write(armdown); // configure arms "down"
servo[i].attach(srvpin[i]); // servos 0 - 6 attached to servo pins
}
delay(1000); // settle servos
// test(); // cycle through segment servos and WS2812B
zero(); // starting number
delay(820); // 180ms to move servos 90 degrees
}
void loop() {
unsigned long servotimeout = 820; // 1000ms/second - 180ms/servomove = 820ms
if (millis() - timer > servotimeout) {
timer = millis(); // set new timer
FastLED.clear();
FastLED.show();
if (count > 9) // upper bounds reached...
count = 0; // reset value
switch (count) { // transition digits
case 0: zro_one(); break;
case 1: one_two(); break;
case 2: two_tre(); break;
case 3: tre_for(); break;
case 4: for_fiv(); break;
case 5: fiv_six(); break;
case 6: six_svn(); break;
case 7: svn_eit(); break;
case 8: eit_nin(); break;
case 9: nin_zro(); break;
default: break;
}
count++; // increase digit count
}
}
void keepseg(byte seg) {
// servo[seg.write(); // No servo in keepseg();
for (int j = 0; j < 3; j++) { // only show in WS2812B
led[seg * 3 + j] = CRGB(191, 255, 255); // blu segment of three WS2812B
}
FastLED.show(); // show buffer
}
void addseg(byte seg) {
servo[seg].write(armup);
for (int j = 0; j < 3; j++) {
led[seg * 3 + j] = CRGB(191, 255, 191); // grn
}
FastLED.show();
}
void remseg(byte seg) {
servo[seg].write(armdown);
for (int j = 0; j < 3; j++) {
led[seg * 3 + j] = CRGB(128, 64, 64); // red
}
FastLED.show();
}
void zero() {
for (byte i = 0; i < 6; i++) { // SEGMENTS minus SEG G
addseg(i); // call "add segment"
}
}
void zro_one() { // changing from ZERO to ONE...
keepseg(segb); keepseg(segc); // keep SEG B and C
remseg(sega); remseg(segd); remseg(sege); remseg(segf); // remove SEG A, D, E, and F
// addseg(); // no addseg()
}
void one_two() { // change from ONE to TWO
keepseg(segb);
remseg(segc);
addseg(sega); addseg(segd); addseg(sege); addseg(segg); // add SEG A, D, E, and G
}
void two_tre() {
keepseg(sega); keepseg(segb); keepseg(segd); keepseg(segg);
remseg(sege);
addseg(segc);
}
void tre_for() {
keepseg(segb); keepseg(segc); keepseg(segg);
remseg(sega); remseg(segd);
addseg(segf);
}
void for_fiv() {
keepseg(segf); keepseg(segc); keepseg(segg);
remseg(segb);
addseg(sega); addseg(segd);
}
void fiv_six() {
keepseg(segc); keepseg(segd); keepseg(segf); keepseg(segg);
remseg(sega);
addseg(sege);
}
void six_svn() {
keepseg(segc);
remseg(segd); remseg(sege); remseg(segf); remseg(segg);
addseg(sega); addseg(segb);
}
void svn_eit() {
keepseg(sega); keepseg(segb); keepseg(segc);
// remseg(); // none
addseg(segd); addseg(sege); addseg(segf); addseg(segg);
}
void eit_nin() {
keepseg(sega); keepseg(segb); keepseg(segc); keepseg(segf); keepseg(segg);
remseg(segd); remseg(sege);
// addseg(); // none
}
void nin_zro() {
keepseg(sega); keepseg(segb); keepseg(segc); keepseg(segf);
remseg(segg);
addseg(segd); addseg(sege);
}
void test() { // cycle through servo/WS2812 segments
int servo90degrees = 180; // milliseconds for 90 degree servo move
for (int i = 0; i < SEGMENTS; i++) { // number of segments
for (int j = 0; j < 3; j++) { // three WS2812 per segment
led[i * 3 + j] = CRGB(0, 0, 255); // blu loaded to buffer
}
FastLED.show(); // display buffer
FastLED.clear(); // clear buffer
servo[i].write(armup); // move servo UP
delay(servo90degrees); // servo takes 2ms per degree to move. 90deg * 2ms/deg = 180ms
servo[i].write(armdown); // move servo DOWN
delay(servo90degrees);
}
FastLED.show();
}
/*
- NEXT to do:
- Four digits
- RTC (clock with "blinking" colon)
- two PCAS9685 (16 channel PWM mux, daisy-chained, two "digits" per mux)
- https://github.com/NachtRaveVL/PCA9685-Arduino
- keep track of which of ten digit (0..9) is in each of four positions (HH:MM)
- 12vdc power supply for Arduino VIN
- 5V/5A BEC/buck converter for SERVOES and first PCA9685A
- Not as genius as this cam-driven model: https://hackaday.io/project/187684-eptaora
*/
diagram.json for the nerds
{
"version": 1,
"author": "",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-arduino-nano", "id": "nano", "top": -33.6, "left": -86.9, "attrs": {} },
{
"type": "wokwi-led-strip",
"id": "strip1",
"top": -333.9,
"left": 316.8,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{ "type": "wokwi-servo", "id": "servo2", "top": -405.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo3", "top": -357.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo4", "top": -309.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo5", "top": -261.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo6", "top": -213.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo7", "top": -165.2, "left": 76.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo8", "top": -117.2, "left": 76.8, "attrs": {} },
{
"type": "wokwi-led-strip",
"id": "strip2",
"top": -218.7,
"left": 316.8,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-led-strip",
"id": "strip4",
"top": -272.6,
"left": 379.5,
"rotate": 90,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-led-strip",
"id": "strip5",
"top": -167,
"left": 379.5,
"rotate": 90,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-led-strip",
"id": "strip6",
"top": -165.6,
"left": 255.5,
"rotate": 270,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-led-strip",
"id": "strip7",
"top": -271.2,
"left": 255.5,
"rotate": 270,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-led-strip",
"id": "strip3",
"top": -104.3,
"left": 318.2,
"rotate": 180,
"attrs": { "pixels": "3", "pixelShape": "circle" }
},
{
"type": "wokwi-text",
"id": "text1",
"top": -57.6,
"left": 297.6,
"attrs": { "text": "GRN - KEPT SEG\nRED - REMOVED SEG\nBLU - ADDED SEG\nBLK - UNCHANGED" }
},
{ "type": "wokwi-text", "id": "text2", "top": -67.2, "left": 249.6, "attrs": { "text": "a" } },
{
"type": "wokwi-text",
"id": "text3",
"top": -115.2,
"left": 249.6,
"attrs": { "text": "f" }
},
{
"type": "wokwi-text",
"id": "text4",
"top": -163.2,
"left": 249.6,
"attrs": { "text": "e" }
},
{
"type": "wokwi-text",
"id": "text5",
"top": -211.2,
"left": 249.6,
"attrs": { "text": "d" }
},
{
"type": "wokwi-text",
"id": "text6",
"top": -259.2,
"left": 249.6,
"attrs": { "text": "c" }
},
{
"type": "wokwi-text",
"id": "text7",
"top": -307.2,
"left": 249.6,
"attrs": { "text": "g" }
},
{
"type": "wokwi-text",
"id": "text8",
"top": -355.2,
"left": 249.6,
"attrs": { "text": "b" }
},
{ "type": "wokwi-vcc", "id": "vcc1", "top": -373.64, "left": 297.6, "attrs": {} },
{ "type": "wokwi-gnd", "id": "gnd1", "top": -48, "left": 258.6, "attrs": {} },
{ "type": "wokwi-vcc", "id": "vcc2", "top": -402.44, "left": 28.8, "attrs": {} },
{
"type": "wokwi-text",
"id": "text9",
"top": -28.8,
"left": 96,
"attrs": {
"text": "EACH SERVO HORN\nIS ATTACHED TO A\nROD (Ø1mm) THEN TO A\n\"SEGMENT\" (see code)"
}
}
],
"connections": [
[ "nano:2", "servo8:PWM", "green", [ "v0" ] ],
[ "nano:GND.1", "servo8:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo7:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo6:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo5:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo4:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo3:GND", "black", [ "v0" ] ],
[ "nano:GND.1", "servo2:GND", "black", [ "v0" ] ],
[ "strip4:DIN", "strip1:DOUT", "green", [ "v0" ] ],
[ "strip4:VDD", "strip1:VDD.2", "red", [ "v0" ] ],
[ "strip4:VSS", "strip1:VSS.2", "white", [ "v0" ] ],
[ "strip4:VSS.2", "strip5:VSS", "white", [ "v0" ] ],
[ "strip6:VSS.2", "strip7:VSS", "white", [ "v0" ] ],
[ "strip7:VSS.2", "strip2:VSS", "white", [ "v-1.4", "h18.4" ] ],
[ "strip4:DOUT", "strip5:DIN", "green", [ "v0" ] ],
[ "strip6:DOUT", "strip7:DIN", "green", [ "v0" ] ],
[ "strip7:DOUT", "strip2:DIN", "green", [ "v-1.4", "h28.4" ] ],
[ "strip7:VDD.2", "strip2:VDD", "red", [ "v-1.4", "h38.4" ] ],
[ "strip7:VDD", "strip6:VDD.2", "red", [ "v0" ] ],
[ "strip3:VSS.2", "strip6:VSS", "white", [ "h0" ] ],
[ "strip3:DOUT", "strip6:DIN", "green", [ "h0" ] ],
[ "strip6:VDD", "strip3:VDD.2", "red", [ "v0" ] ],
[ "strip3:VSS", "strip5:VSS.2", "white", [ "h0" ] ],
[ "strip3:DIN", "strip5:DOUT", "green", [ "h0" ] ],
[ "strip3:VDD", "strip5:VDD.2", "red", [ "h0" ] ],
[ "strip5:VDD", "strip4:VDD.2", "red", [ "v0" ] ],
[ "vcc1:VCC", "strip1:VDD", "red", [ "v0" ] ],
[ "gnd1:GND", "strip1:VSS", "black", [ "v0" ] ],
[ "vcc2:VCC", "servo2:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo3:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo4:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo5:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo6:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo7:V+", "red", [ "v0" ] ],
[ "vcc2:VCC", "servo8:V+", "red", [ "v0" ] ],
[ "nano:3", "servo7:PWM", "green", [ "v0" ] ],
[ "nano:4", "servo6:PWM", "green", [ "v0" ] ],
[ "nano:5", "servo5:PWM", "green", [ "v0" ] ],
[ "nano:6", "servo4:PWM", "green", [ "v0" ] ],
[ "nano:7", "servo3:PWM", "green", [ "v0" ] ],
[ "nano:8", "servo2:PWM", "green", [ "v0" ] ],
[ "nano:10", "strip1:DIN", "green", [ "v-384", "h316.8", "v96" ] ]
],
"dependencies": {}
}
A link, until Wokwi fades away:
