Bauble
4th November 2025
The Bauble is a spherical festive decoration with 16 coloured LEDs that you can program to flash in different patterns:
The Bauble is a flashing decoration based on an AVR32DD28
that can display a variety of patterns.
It consists of two identical circular PCBs that slot together to create a three dimensional circuit; each PCB contains eight LEDs of four different colours, and one of the PCBs houses the microcontroller and battery,
Introduction
The Bauble started with the idea of creating a three-dimensional circuit by combining PCBs. It is constructed from two copies of the same circular PCB that slot together:

The two boards that slot together to make the Bauble, using 3mm through-hole LEDs.
The motherboard on the left houses the microcontroller and a button cell on the reverse, in addition to resistors and eight LEDs, whereas the daughterboard on the right is only populated with resistors and LEDs. The two PCBs interconnect via nine edge connections, which also hold the boards together.
It is based on an AVR32DD28, a microcontroller with 32Kbytes of flash memory and 8Kbytes of RAM. To program it you connect to it from a computer via a six-pin header, and you can then download a program from the Arduino IDE.
The LEDs
The boards use a total of 16 LEDs, four of each of the four colours red, orange, yellow, and green. The LEDs are distributed so that no two LEDs of the same colour are next to each other vertically or horizontally.
You can either use 3mm through-hole LEDS as in the above photographs or 0805 surface-mount LEDs. A third option is side-illumination surface-mount 0805 LEDs:

Using side-illumination 0805 surface-mount LEDs.
A further eight LEDs are on the reverse sides of the boards.
How the Bauble works
Pressing the push button wakes up the Bauble and lets you step through several predefined patterns. One of the options is to put the Bauble to sleep.
Once you choose a pattern it will be displayed for an hour at the same time each day, with the Bauble in low-power sleep for the intervening time. The current consumption in sleep is about 3µA, so a button cell should last for years. You can change these settings in the program.
You can design your own patterns by connecting a Serial FTDI programming board to a six-pin header. The firmware I've provided includes the following seven different patterns:
The patterns
The following table lists the built-in patterns:
| Pattern | Description |
| Two-colour fade | The LEDs light in the sequence: green, red, orange, yellow, fading gradually between each pair of colours. |
| Two-colour fade 2 | The LEDs light in the sequence: green, red, orange, yellow, fading gradually between each pair of colours. |
| Twinkle | All the LEDs fade on and off in a random twinkling pattern. |
| Sparkle | The LEDs flicker in a rapid sparkling pattern. |
| Rings | The LEDs light up in rings that move up the Bauble. |
| Random | The LEDs light up in a random sequence. |
| Pulsate | The LEDs all pulsate on and off with slight variations. |
| Sleep | All the LEDs give a brief flash and then stay off, and the Bauble remains in sleep until you press the button again. |
The patterns are designed to have an average current consumption of about 12mA, so a CR2032 cell should last for about 18 hours running continuously, or for 18 days running for 1 hour a day.
The circuit
Here's the circuit of the Bauble:

Circuit of the Bauble motherboard, based on an AVR32DD28.
The daughterboard is identical, but only populated with the LEDs and 220Ω resistors.
Processor
I chose to base the PCB on the version of the AVR32DD28 in a surface-mount SOIC-28 package, as it is reasonably easy to solder by hand using a fine-tipped soldering iron. The Bauble program uses less than 10% of the memory on the AVR32DD28, and so will work with the AVR16DD28 too.
LEDs
There are a total of 16 LEDs, four mounted on each side of the two boards. There are several options for the LEDs:
- 0805 surface-mount LEDs. These are the easiest to mount, but the disadvantage is that the light is emitted vertically, and so is not so visible from a distance. These are widely available from many suppliers.
- 3mm through-hole LEDs, mounted pointing out from the centre of the Bauble. These give a good appearance, but are a bit fragile. Order a few spares, because it's quite easy to destroy them by overheating them when soldering them. They are widely available from many suppliers.
- 0805 surface-mount side-illumination LEDs. These give good illumination from a distance, but are a bit fiddly to mount. They are available from AliExpress [1]:

Follow the above photographs to see how to mount the different colours. The negative terminal of each LED is on the side pointing towards the equator if you think of the Bauble as a globe.
Battery
The Bauble is designed to work with a 3V 12mm coin cell type CR2032, which has a capacity of about 225mAh. The coin cell fits in an SMD 12mm coin cell holder [2].
The Schottky diode protects the button cell if you leave it fitted when connecting the Bauble to the 5V on the FTDI connector. The one I chose has a voltage drop of about 200mV.
Other components
The button is the same as I've used on several of my projects, a widely available miniature SMD push button [3]. The resistors and capacitors are 0805 surface-mount parts.
Here's the full parts list (click to expand):
► Parts list
Construction
I designed the PCB in Eagle and sent different versions to PCBWay [4] or JLCPCB [5] for fabrication.
The AVR32DD28 and other surface-mount components are reasonably easy to solder by hand using a fine-tipped soldering iron. Alternatively you can use a hot air gun; I used a Youyue 858D+ hot air gun set to 325°C.
Once you've fitted all the SMD components, solder the coin cell holder onto the back of the board using a conventional soldering iron. Note that if you are making the Bauble for a child, please apply glue to the coin cell holder, after inserting the cell, so there's no chance of the coin cell being removed and swallowed:

The reverse of the Bauble motherboard, showing the CR2032 battery holder.
Header pins
A row of six header pins allows you to connect an FTDI board or FTDI cable to program the Baudle. The header pins somewhat spoil the appearance of the Bauble, so I've designed it with staggered holes, with each hole shifted 8 mil (~0.2 mm) off-centre. This allows you to push-fit a row of six header pins, and they will stay firmly connected without soldering until you've programmed the Bauble. You can then remove them when they're no longer needed. Of course you also have the option of soldering them if you prefer, for a permanent solution.
Interconnecting the boards
I tried several alternative ways of interconnecting the two PCBs. My first attempt was to use an aligned set of PCB pads, but it proved impossible to get the solder to flow between them to make a connection. I also tried to design mating connectors that would allow you to plug the boards together, but I couldn't find a satisfactory way of doing it. My final approach is to provide a matching row of nine header holes which can be connected with thin tinned copper wire.
I used thin 36 SWG tinned copper wire, also sold as 4.5A fuse wire, but it's not critical. I wound it in a zig-zag between the holes, like lacing a shoe, soldered the pads, and then clipped off the loops leaving just the links between corresponding pads.
Testing the Bauble
I recommend testing the Bauble motherboard on its own before assembling the two boards, because it will be very difficult to diagnose faults or change components once the two boards are interconnected. To do this, install a bootloader in the processor, and then run the test program, as described in the following sections.
Installing a bootloader
Although you can program the Bauble via the UPDI pin, I recommend installing a bootloader so you can subsequently program the Bauble via the FTDI port. This has the advantage that you can use the FTDI port for debugging, by printing to the Serial Monitor using Serial.print() statements.
- Install Spence Konde's DxCore from GitHub: see DxCore.
Then, in the Arduino IDE:
- Choose the AVR DD-series (Optiboot) option under the DxCore heading on the Board menu.
- Check that the subsequent options are set as follows (ignore any other options):
Board: "AVR DD-series (Optiboot)"
Chip: "AVR32DD28"
Clock Speed: "24 MHz internal"
You can leave the other settings at their defaults.
- Connect a UPDI programmer to the GND and +5V connections on the 6-pin header.
- Hold a cable in contact with the UPDI pad on the underside of the microcontroller on the Bauble.
The recommended option is to use a USB to Serial board, such as the SparkFun FTDI Basic board [6], or a USB to Serial cable [7], connected with a 4.7kΩ resistor as follows:

- Set Programmer to the first of the "SerialUPDI - 230400 baud" options.
- Select the USB port corresponding to the USB to Serial board in the Port menu.
- Choose Burn Bootloader from the Arduino IDE Tools menu to install the bootloader in the AVR32DD28.
Uploading programs via Serial
Now you've installed a bootloader you can upload programs via the serial port, which is accessed via the row of six header pins on the Bauble.
- Leave the Arduino IDE set to the same AVR DD-series (Optiboot) option as before, with the same settings. This time the Programmer option is irrelevant.
- Connect an FTDI board or FTDI cable to the FTDI header on the Bauble. This can be the same FTDI board you used for the UPDI programming.
- Connect other end of the FTDI cable to your computer's USB port, and select it from the Port menu.
- Click Upload to upload a program to the AVR32DD28.
Running the test program
The 16 LEDs correspond to Arduino pin numbers 2 to 9 on the motherboard, and 13 to 20 on the daughterboard.
Upload and run the following test program:
void setup() {
for (int i=2; i<=9; i++) pinMode(i, OUTPUT);
}
void loop() {
for (int i=2; i<=9; i++) {
digitalWrite(i, HIGH);
delay(1000);
digitalWrite(i, LOW);
delay(1000);
}
}
This should light each of the LEDs on the motherboard in turn. If an LED doesn't light up the most likely explanation is that it's connected the wrong way round.
The program
Here are some details of the Bauble program:
PWM control of the LEDs
To make the visual effect of the Bauble as interesting as possible I wanted to be able to control the brightness of each LED independently. The usual way to do this on an AVR chip is to use the Timer/Counter peripherals to drive I/O pins with a PWM (pulse-width modulated) waveform. However the 28-pin chips I was planning to use, such as the AVR32DD28, only support a maximum of 11 simultaneous PWM outputs [8], and I need to be able to control 16.
The solution is to implement PWM in software, using an ISR (interrupt service routine) called by a timer interrupt. To minimise the time taken by the ISR I decided to access the ports directly, rather than use the Arduino convenience functions such as digitalWrite().
For flexibility the I/O pins are defined by two arrays; a port[] array specifying the port address that each pin is on, and a pin[] array defining the bit in the port that the pin is on:
PORT_t *port[16] = {&PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTC, &PORTC,
&PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTF};
uint8_t pin[16] = {2, 3, 4, 5, 6, 7, 0, 1,
1, 2, 3, 4, 5, 6, 7, 0};
The level of each LED is defined as a value between 0 and 255 by a byte array Level[]:
uint8_t Level[16], Buffer[16];
A second array is used as a buffer; see below.
The PWM routine, PWM(), is called from the ISR. It counts down a ramp from 255 to 0. For each LED, when Level[led] matches the ramp counter, the LED is turned on. All the LEDs are turned off when ramp is zero:
void PWM () {
static uint8_t ramp;
if (ramp == 0) {
PORTA.OUTCLR = 0xfc; // All outputs low
PORTC.OUTCLR = 0x03;
PORTD.OUTCLR = 0xfe;
PORTF.OUTCLR = 0x01;
for (uint8_t led=0; led<16; led++) {
Buffer[led] = Level[led];
}
} else { // ramp != 0
for (uint8_t led=0; led<16; led++) {
if (Buffer[led] == ramp) port[led]->OUTSET = 1<<pin[led];
}
}
ramp--;
}
Choosing the interrupt rate
What rate should the interrupt timer run at? The slowest rate of flashing of an LED that will not give flicker is about 50Hz. The ISR needs to be called 256 times for each LED, which gives a rate of 256 x 50 Hz, or 12.8kHz. In fact I chose a slightly higher rate of 15.625kHz, using Timer/Counter TCB0; here's the code:
void SetupInterrupt () {
TCB0.CCMP = 1535; // 24MHz/1536 = 15625Hz
TCB0.CTRLA = TCB_CLKSEL_DIV1_gc | TCB_ENABLE_bm; // Divide timer by 1
TCB0.CTRLB = 0; // Periodic Interrupt
TCB0.INTCTRL = TCB_CAPT_bm; // Enable interrupt
}
Double buffering
With an earlier version of the PWM() routine there was a problem that the LEDs occasionally flickered when being faded gradually on and off. I traced the problem to the way that the PWM is running asynchronously from the LED pattern function.
Imagine that Level[led] is at 128. As the ramp goes from 255 to 0, when it passes 128 it will turn the LED on, and the LED will remain on until ramp = 0.
Now imagine that when ramp=129 I change Level[LED] to 130. Now the LED will remain off from ramp=129 to ramp=0, and then from ramp = 255 to ramp=130, causing a flicker.
The solution is to double-buffer the Level[] array using a second array Buffer[]. The levels are copied from Level[] to Buffer[] at the start of each loop, when ramp is zero, ensuring that no changes occur during the active part of the ramp.
Making the push button work as an on/off switch
The push button is connected to reset, and by defining a variable Flag that doesn't get initialised on reset, we can use the push button to step between patterns. The number of patterns, plus one for the sleep option, is defined by Cases:
const int Cases = 8;
This is the definition of Flag:
uint8_t Flag __attribute__ ((section (".noinit")));
On reset Flag is incremented:
void setup () {
Flag = (Flag+1) % Cases;
...
}
Its value determines which pattern is chosen, using a switch statement.
Sleep timer
The program also uses the AVR32DD28's Periodic Interrupt Timer to allow you to program the Bauble to wake up from sleep after a specified period of time. It is set up by the routine SetupRTC():
void SetupRTC () {
// Internal 32.768kHz Oscillator
RTC.CLKSEL = RTC_CLKSEL_OSC32K_gc;
// RTC Clock Cycles 32768, enabled ie 1Hz interrupt
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;
RTC.PITINTCTRL = RTC_PI_bm; // Periodic Interrupt enabled
}
This generates an interrupt every second that counts down a Seconds counter:
ISR(RTC_PIT_vect) {
RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag
Seconds--;
}
This is used while a pattern is being displayed to put the Bauble to sleep after 1 hour and wake it up again at the same time the following day.
Defining the patterns
To take advantage of the PWM control on every LED the Bauble uses a single expression to define the brightness of each of the 16 LEDs. The expression can reference four floating-point variables, and calculates a floating point number that sets the level of that LED.
I measured that with a 24MHz clock rate the AVR32DD28 processor is fast enough to recalculate a floating-point pattern expression at the PWM interrupt rate.
Here are details of each of the variables:
float lat - latitude
A value of 0 corresponds to the south pole of the bauble, and a value of 1 to the north pole.
The bottom ring of four LEDs are assigned the value 1/8, the next ring the value 3/8, the next ring the value 5/8, and the top ring the value 7/8.
float lon - longitude
A change of value from 0 to 1 corresponds to a complete rotation around the bauble.
The first vertical row of four LEDs are assigned the value 1/8, the next row the value 3/8, the next row the value 5/8, and the last row the value 7/8.
float col – colour
The col parameter assigns each LED a value in the range from 0 to 1, with the four LEDs of each colour adjacent:
| 1/32 | 3/32 | 5/32 | 7/32 | 9/32 | 11/32 | 13/32 | 15/32 | 17/32 | 19/32 | 21/32 | 23/32 | 25/32 | 27/32 | 29/32 | 31/32 |
| red | red | red | red | orange | orange | orange | orange | yellow | yellow | yellow | yellow | green | green | green | green |
float t – time
The t parameter increases continuously upwards from 0 with time, where 1 corresponds to 1 second.
Expression value
The expression should give a floating-point value between 0 and 1.0 specifying the brightness to be displayed at the position on the bauble specified by the parameters. The function therefore defines the light display over space, colour, and time, making it easy to create smooth, dynamic display patterns with a single function.
Main program
The main loop repeatedly evaluates the function for each of the 16 LEDs. Here's a simplified version which displays a single pattern, Sparkle:
void loop () {
Seconds = SleepAfter;
uint64_t start = millis();
do {
uint64_t elapsed = millis() - start;
float t = (float) elapsed / 1000.0;
for (uint8_t i=0; i<16; i++) {
float lat = Latitude[i]; float lon = Longitude[i]; float col = Colour[i];
float led = (float)(i + 0.5)/16.0;
float value;
value = 1 - tri((t*(1+col*0.1))/5.0) * 8.0; // Sparkle
value = pow(value, 2.2);
Level[i] = 255 * clamp(value, 0.0, 1.0);
}
}
}
In the full version a switch statement selects one of the eight patterns.
Resources
Here's the Bauble program: Bauble Program.
Get the Eagle files for the PCB here: https://github.com/technoblogy/bauble.
Or order boards from OSH Park here: Bauble.
Acknowledgements
Thanks to Chris Jordan for help designing the Bauble and the space-time pattern functions, and providing feedback on early prototypes.
Update
8th November 2025: Added a photograph of the reverse of the Bauble motherboard, showing the battery holder.
- ^ Side-Emitting LEDs 0805/0802 on AliExpress
- ^ Coin Cell Battery Holder - 12mm (SMD) on Farnell.
- ^ Mini Push Button Switch - SMD on SparkFun.
- ^ PCBWay PCB prototype service.
- ^ JLCPCB PCB prototype service.
- ^ SparkFun FTDI Basic Breakout - 5V on Sparkfun.
- ^ FTDI Serial TTL-232 USB Cable on Adafruit.
- ^ AVR32DD28 on SpenceKonde/DxCore
blog comments powered by Disqus
