► Games

► Sound & Music

► Watches & Clocks


► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28



► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon


RSS feed

Combination Lock Using CCL

30th November 2020

This project is a digital combination lock that only lets you activate a circuit if you know the 8-bit key. It demonstrates an application of Configurable Custom Logic, a feature included in the latest AVR DA-series chips from Microchip:


Combination Lock implemented using Configurable Custom Logic on an AVR128DA28.
The key is 10010011.

As a comparison, I implement the same application using a conventional program, and discuss the pros and cons of each approach.


The latest AVR processors released by Microchip all include a new feature called Configurable Custom Logic, or CCL, and since their release I've been trying to think of compelling applications of this feature. It allows you to construct an arbitrary logic circuit that works independently of the processor. This project demonstrates one practical application, using the logic functions to construct an arbitrary logic expression.

CCL is included in three ranges of AVR devices: the new ATtiny 0-series and 1-series, the new ATmega 0-series, and the AVR DA series which Microchip announced in May of this year. I decided to use one of the AVR DA series; although most of the parts are in surface-mount packages, they have fortunately made the AVR128DA28 available in a 28-pin DIP package which is ideal for experimenting with on a prototyping board. Spence Konde already has an Arduino core called DxCore available for the DA series [1], and this includes instructions for making yourself a suitable UPDI programmer.

By the way, if like me you're familiar with the way in which the old ATtiny and ATmega processor registers are configured, you may find the naming conventions used in the DA series quite confusing. I recommend reading Microchip's document Getting Started Writing C-code for XMEGA, which explains the conventions, and the rationale behind them; thanks to @awneil on AVR Freaks for suggesting this.

CCL logic elements

To understand how to configure the CCL I found I needed to spend some time reading the appropriate chapter in the datasheet [2]. Essentially the CCL provides configurable logic elements, sequencing blocks such as flip-flops and latches, and signal conditioning such as filters and edge-detectors. In this project I am just using the logic elements.

Each configurable logic element has three inputs and one output, and you can construct more complex circuits by combining these. The AVR DA series devices provide four logic elements on the 28-pin and 32-pin parts, and six logic elements on the 48-pin and 64-pin parts. The following table shows the pins corresponding to each of the four logic elements available on the smaller packages, and the pin numbers in brackets show the I/O pins available on the 28-pin parts:

LUT0 PA0 (22) PA1 (23) PA2 (24) PA3 (25)
LUT1 PC0 (2) PC1 (3) PC2 (4) PC3 (5)
LUT2 PD0 (6) PD1 (7) PD2 (8) PD3 (9)
LUT3 PF0 (16) PF1 (17) PF2 PF3

Fortunately the 28-pin parts have enough inputs assigned to pins to allow you to cascade four of the logic elements and make an 8-input general logic gate:


Arrangement of CCL logic elements to implement the Combination Lock.

The Combination Lock uses this configuration to allow you to set an 8-bit input, using a DIP switch, and only if you set the correct combination will the output go high and light the LED.

The circuit

Here's the circuit of the Combination Lock, using a similar layout to the circuit on the prototyping board:


Circuit of the Combination Lock using an AV128DA28.

The processor is an AVR128DA28 in an SPDIP package [3], and the DIP switch is an 8-way single-throw type [4].

Combination Lock using CCL

Here's the CCL version of the Combination Lock. This is all done in setup().

Configuring the inputs

First we define the eight inputs with pullups, so when not switched to GND by the DIP switch they read as logic high:

  PORTA.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PA0
  PORTA.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PA1
  PORTC.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PC0
  PORTC.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PC1
  PORTD.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PD0
  PORTD.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PD1
  PORTF.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PF0
  PORTF.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PF1

Configuring the Look-Up Tables

Next we configure the four LUTs to take IN0 and IN1 from external pins, and IN2 from a link to the output of the next LUT in all cases except LUT3:

  CCL.LUT0CTRLB = CCL_INSEL0_IN0_gc | CCL_INSEL1_IN1_gc; // Inputs 0 and 1 from pins
  CCL.LUT0CTRLC = CCL_INSEL2_LINK_gc;                    // Input 2 from next LUT
  CCL.LUT1CTRLB = CCL_INSEL0_IN0_gc | CCL_INSEL1_IN1_gc; // Inputs 0 and 1 from pins
  CCL.LUT1CTRLC = CCL_INSEL2_LINK_gc;                    // Input 2 from next LUT
  CCL.LUT2CTRLB = CCL_INSEL0_IN0_gc | CCL_INSEL1_IN1_gc; // Inputs 0 and 1 from pins
  CCL.LUT2CTRLC = CCL_INSEL2_LINK_gc;                    // Input 2 from next LUT
  CCL.LUT3CTRLB = CCL_INSEL0_IN0_gc | CCL_INSEL1_IN1_gc; // Inputs 0 and 1 from pins
  CCL.LUT3CTRLC = CCL_INSEL2_MASK_gc;                    // Input 2 masked

Defining the truth tables

For each LUT we need to define a truth table, specifying what state the output should be for the eight possible states of the inputs. The truth table is specified by an 8-bit number; each bit specifies the output for one set of states of the inputs. So, for example, bit 6 in the truth table specifies the state of the output for IN2 = 1, IN1 = 1, and IN0 = 0.

For our combination lock we only want the output to be '1' if the inputs IN2, IN1, and IN0 are in the correct states, so the truth table is an eight-bit binary number with one '1' bit. An easy way to calculate it is:

1 << 0bxxx

where xxx is the binary number representing the inputs on IN2, IN1, and IN0 respectively.

As an example we'll define the Key as:

const int Key = 0b10010011;

So the truth table for LUT3 is:

1 << 0b010

because IN2 is ignored (so zero), and IN1 and IN0 are the top two bits of the Key, '10'.

The truth table for LUT2 is:

1 << 0b101

because IN2 must be '1' from the previous LUT, and IN1 and IN0 are the next two bits of the Key, '01'. LUT1 and LUT0 are calculated in the same way.

We can define the truth tables to cater for any specified key as follows:

  CCL.TRUTH0 = 1<<(0b100 | (Key & 0b11));                // Truth table for LUT0
  CCL.TRUTH1 = 1<<(0b100 | (Key>>2 & 0b11));             // Truth table for LUT1
  CCL.TRUTH2 = 1<<(0b100 | (Key>>4 & 0b11));             // Truth table for LUT2 
  CCL.TRUTH3 = 1<<(0b000 | (Key>>6 & 0b11));             // Truth table for LUT3

For the particular key given above these are equivalent to:

  CCL.TRUTH0 = 1 << 0b111;
  CCL.TRUTH1 = 1 << 0b100;
  CCL.TRUTH2 = 1 << 0b101;
  CCL.TRUTH3 = 1 << 0b010;

Enabling the LUTs and CCL

Then finally we enable the LUTs, and the whole CCL:

  CCL.LUT0CTRLA = CCL_ENABLE_bm | CCL_OUTEN_bm;          // LUT0 and output to pin
  CCL.LUT1CTRLA = CCL_ENABLE_bm;                         // LUT1
  CCL.LUT2CTRLA = CCL_ENABLE_bm;                         // LUT2
  CCL.LUT3CTRLA = CCL_ENABLE_bm;                         // LUT3
  CCL.CTRLA = CCL_ENABLE_bm | CCL_RUNSTDBY_bm;           // CCL, run in standby

For LUT0 we also specify that its output is sent to a pin. Note that there's no need to also define the pin as an output; this happens automatically.

This final step must be performed last, because the LUT control registers are protected from being configured while the LUTs are enabled.

Once the CCL is enabled, the logic elements perform their task irrespective of what the program is doing.

Here's the whole Combination Lock CCL version: Combination Lock CCL.

Combination Lock using a program

Now, for comparison, here's how I would implement the same application using a program.

First, here's the key:

const int Key = 0b10010011;

Next, as before, we define pullups on the eight inputs, and define PA3 as an output:

void setup() { 
  // Configure I/O pins appropriately
  PORTA.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PA0
  PORTA.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PA1
  PORTC.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PC0
  PORTC.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PC1
  PORTD.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PD0
  PORTD.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PD1
  PORTF.PIN0CTRL = PORT_PULLUPEN_bm;                     // Pullup on PF0
  PORTF.PIN1CTRL = PORT_PULLUPEN_bm;                     // Pullup on PF1
  PORTA.DIR = PIN3_bm;                                   // Pin PA3 as output

Then, in loop(), we read the settings of the I/O lines corresponding to the DIP switch, and if they match Key light the LED:

void loop() {
 int bits10 = PORTA.IN & (PIN1_bm | PIN0_bm);
 int bits32 = PORTC.IN & (PIN1_bm | PIN0_bm);
 int bits54 = PORTD.IN & (PIN1_bm | PIN0_bm);
 int bits76 = PORTF.IN & (PIN1_bm | PIN0_bm);
 int combination = bits76<<6 | bits54<<4 | bits32<<2 | bits10;
 if (combination == Key) PORTA.OUT |= PIN3_bm;          // Write PA3 high
 else PORTA.OUT &= ~PIN3_bm;                            // Write PA3 low

I could have made it a bit simpler by connecting the DIP switch to eight I/O lines in the same port, such as PD0 to PD7 in the AVR128DA28, but I wanted to use the same prototyping board layout.

Here's the whole Combination Lock program version: Combination Lock Program.

Compiling the programs

Compile the programs using Spence Konde's Dx Core on GitHub. Choose the AVR DA-series (no bootloader) option under the DxCore heading on the Board menu. Check that the subsequent options are set as follows (ignore any other options):

Chip: "AVR128DA28"
Clock Speed: "24 MHz internal"
Programmer: "jtag2updi (megaTinyCore)"

Then upload the program to the processor on the breadboard using a UPDI programmer connected to the GND, +5V, and UPDI pins. You can make a UPDI programmer from an Arduino Uno, or other ATmega328P-based board, as described in Make UPDI Programmer.

The programs should also work perfectly well on any other AVR DA-series devices, and at other clock speeds.


Obviously this Combination Lock is more of a demonstration than a practical security device, as it wouldn't take long to test all 256 combinations. However, it could be made more secure in conjunction with a button to submit a test combination, together with an increasing time delay penalty for incorrect attempts. Alternatively, using all six LUTs in the larger AVR DA devices you could create a combination lock with a key of up to 13 bits.

Pros and cons of each approach

Using the Configurable Custom Logic is a lot more work to set up, but once configured it provides the application without any load on the processor, which could be performing a highly intensive task, such as outputting a digital waveform, without any interruption from the operation of the combination lock.

Another advantage of the CCL approach is that it continues to work when the processor is asleep. I measured the current consumption with the LED not lit: for the program version it was 6.9mA, whereas for the CCL version in power-down sleep mode it was only 1.3mA.

There are however some limitations of the CCL approach. Because the logic circuit must be constructed from three-input components some logic circuits can't be implemented. One example would be a combination lock that is only activated by an 8-bit number with the same number of '0's and '1's, such as 0b10110001. Implementing this using the program version would be a relatively simple extension.

In conclusion, CCL is probably going to be most useful in applications that would normally need a few additional logic gates, but which can now be implemented entirely with a single microcontroller chip.


1st December 2020: Corrected the information about the number of LUTs on the different DA-series parts.

  1. ^ DxCore on GitHub.
  2. ^ AVR128DA28/32/48/64 datasheet on Microchip.
  3. ^ AVR128DA28-I/SP on
  4. ^ 8 Way Through Hole DIP Switch on RS-Online.

blog comments powered by Disqus