Blinking LEDs – achieving POV

Part of the Sudoku Solver project… and the valuable lessons I am learning

Some time ago, I started thinking about building a SUDOKU solver. I set some goals for the project. One of the goals was that I will build the entire Sudoku matrix using modules – each one incorporating nine 7-segment displays and preferably only a single microcontroller. Each module will in turn communicate with the rest of the modules through a serial protocol (I2C).

One of the constraints I have imposed onto myself, was that ideally, I would like to use an Atmel micro – one of the ATtiny series. Yet another constrain was that I would program the module outside of the Arduino ecosystem.

Now… although it’s not impossible to use a micro from the ATtiny microcontrollers, it meant that if I was to achieve my goal of having one microcontroller, I would have to use dedicated (7-segment) LED driver ICs. One such IC is the MAX7219 from Maxim Integrated (or an excellent drop-in replacement such as the AS1100 from Austria Micro Systems). With this one IC, I could drive up to eight 7-segment displays and I could add just one more and drive eight more!

But even in such a case, I am still short on IO pins to control which one is enabled at what time – or all at once. Plus, this IC wasn’t available at my local store and I didn’t want to make an online order and wait. Things are as bad as they are with the C19 pandemic!

So I decided to go simple. Very simple. Here’s the circuit I came up with:

I decided to go with the ATmega328 – simply because it has just the right IO count I need. As you may observe, I had used all but 2 pins: pin 3 or TXD (which I intend to use for debug) and pin 28 or PC0 (which is an analog capable pin for which later on I managed to find a use).

The CD4511 is a common BCD to 7-segment decoder and LED driver.

With only one CD4511 I am able to control only one 7-segment display without the DP (decimal point) which I don’t need anyhow for this purpose. One 7-segment display at a time! Which is why the EN1..EN9 digital outputs are for! If I turn them fast enough and one at a time, I should get the impression all nine 7-segment display are on. At least in principle, this is what POV suggests.

The three push buttons are there to ‘scroll’ through the displays (UP/DOWN) and also to set a digit (SET). The 7-segment displays are all common anode ones – none of them able to sink the current I want and the brightness I expect. Hence the common 2N3904 NPN BJTs:

What else? I have a RESET button and six 10k pull-up resistors – one for the RESET, three more for the 3 other push buttons and two more for the SDA and SCL I2C lines.

And this is when I realized my first couple of lessons:

Lesson 1 – DO NOT rely solely on a simulated circuit. Build one instead!

The expected results from the simulation will be as good as the model itself! That’s why working on real HW is better, so build a prototype circuit! While I was working in my simulated environment, the 7-segment displays were not displaying the desired digits in the order they were supposed to, because the EN pins were not activated in the order they were supposed to – for no apparent reason!? Well not entirely true. Here I decided to “cheat” and used the Arduino UNO to quickly test my proof of concept and used the Arduino IDE and its libraries – despite the fact I set a constraint at the beginning I would use bare metal code for the microcontroller of my choice.

Lesson 2 – The Arduino IDE produces strange results (which are not that strange if you have the experience and know were to look – and I am not that experienced :))!

I decided to build a small test circuit around my Arduino Uno and test the functionality related to the EN pins only. Basically LEDs and 220Ohm current limiting resistors for each LED and a pot to change the frequency at which I toggle the LEDs interchangeably:

Here are the sketches I initially used. V1 worked fine. I wanted to refactor the code and reduce the number of lines. V2 didn’t work as I would expected it – the moment delayTime approached zero, instead of the illusion all 9 LEDs light up at the same time – LED8 (the one connected to pin 8 of the UNO) seemed as if dimmed 90% or completely OFF. At that point I went from a for loop to a while loop with V3. Same effect!

Then I followed this video guide and this one, on how to setup Atmel Studio to work with the Uno and program it’s ATmega328 in bare-metal C. In a nutshell, this is what my External Tools setup in Atmel Studio looks like:

I basically followed the instructions from the guides mentioned above. The only difference I made, is copy the AVR tools supplied with the Arduino IDE in a separate folder.

TitleArduino_UNO_Bootloader
Commandc:\users\vdjepovs\Bin\AVR/bin/avrdude.exe
Arguments-C”C:\Users\vdjepovs\Bin\AVR/etc/avrdude.conf” -v -patmega328p -carduino -PCOM5 -b115200 -D -Uflash:w:”$(ProjectDir)Debug\$(TargetName).hex”:i
Initial directory$(ProjectDir)

The link to the two programs I tried out (V4 and V5 – the final one) and both with the expected results (the link to the entire repository)!

#define F_CPU 16000000UL
#define __DELAY_BACKWARD_COMPATIBLE__

The first define is the CPU frequency of the ATmega328 and it’s a required one – if we want an accurate delay timing. If we omit the second define, then compiling the source code would fail at:

_delay_ms(delay);

This is because the _delay_ms function expects constant values at compile time.

Then we set the Data Direction Registers on Port D and B for the pins we wish to use as digital outputs:

DDRD = (1 << PD0) | (1 << PD2) | (1 << PD3) | (1 << PD4) | (1 << PD5) | (1 << PD6) | (1 << PD7);
DDRB = (1 << PB0) | (1 << PB1);

This is effectively as writing:

DDRD |= 0b11111101;
DDRB |= 0b00000011;

Then for turning each individual LED ON/OFF, inside the while loop, we have two for loops – one for the pins on PORTD and another one for the used pins on PORTB. For example:

for (uint8_t pin = PD0; pin <= PD7; pin++)
{
	// We skip all these pins
	if (pin == PD1)
	{
		continue;
	}
	
	PORTD |= 1 << pin;
	_delay_ms(delay);
	PORTD &= 0 << pin;
	_delay_ms(delay);
}

Basically, we are looping with the pin variable trough all the pins on PORTD (in the example above) skipping those we do not bit manipulate (PD1 in the example), and when we want to set the state of the specific pin, we bit shift to the left the value 1 pin number of times and we ‘or it’ with the previous value in the PORTD register! When we want to reset the state of that same pin, we bit shift to the left the value 0 pin number of times and we ‘and it’ with the previous value in the PORTD register. In between each set/reset, we wait delay amount of milliseconds and we set the value of the delay variable with the potentiometer.

Now, working with the pins connected to the ADC – in our case the remaining free one PC0. This is a bit trickier than setting/resetting a pin. You either read through the datasheet manual for the ATmega328, or you follow the various tutorials available online (for example, this video tutorial and these web pages Arduino to AVR-C Reference Guide, Using the ADC and Analog Input were most informative to me).

Outside of the main while loop (or the setup section):

ADMUX = (1 << REFS0) | (1 << ADLAR);
ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = 0x00;

Inside the main while loop:

ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC)); // Wait until the ADC conversion has finished
delay = ADCH;

For an explanation of how the analog part works, I highly recommend visiting the links referenced above. The only amendment I would like to make here is that when setting the ADC Multiplexer Selection Register or ADMUX, the four least significant bits (MUX3, MUX2, MUX1, MUX0) in my case are all 0 as I am using PC0. Furthermore, even though the ATmega328 offers a 10-bit resolution, I was happy with just 8 bits (hence delay = ADCH which gives me very close to 0-255ms between each change of state on the EN pins).

And with that, I was able to get what I wanted.

Step 1 of the Sudoku solder completed