Driving a 7 segment LED display

Continuing my last project with an end goal of creating a Sudoku solver, I am breaking up the project into smaller – more manageable chunks, and this time by driving a single 7 segment LED display.

As I have already stated before, building a prototype circuit where you can test your firmware is always a better alternative (if you can afford it) than to rely completely on a simulated one – as this inherently leads to other problems simply because a simulated environment is “more forgiving” than a physical one.

It’s because of this approach I took early in the project, I already identified some errors and experienced problems because of that. For example, in my previous post on the topic, I have forgotten to include the current limiting resistors for the 7 segment display, resulting in damaging the one I had used for the first prototype – which was with longer pins and therefore could plug straight into the protoboard. For my second attempt (which really feels as a consequence due to a beginner’s mistake) I had to improvise and solder some header pins on the new one before I was able to reliably use it with the protoboard.

I ran out of physical space to insert a proper switch, hence I improvised: that yellow wire in the top right corner goes from a-3 to the analog pin A2 of the UNO or PC2 on the ATmega328P and through the 10k resistor (across j-3 and j6) is pulled to ground. The switching of the state of A2 I actually change with the red wire (bottom right corner) with one end connected to the A2 pin, while the other one to the + rail (state HIGH) or one of the free connections e.g. h-2 (state LOW).

All of this makes more sense if looking directly at the circuit diagram:

Pretty straight forward so I won’t delve too much into the HW details and concentrate more on the SW side!

As always, all of the source code is on my BitBucket repository. I went through 6 different while trying different things. The one I will try and explain is version 6.

Here goes…

Let’s talk about the file setup first:

main.c
api.h
api.c
hal.h

hal.h represents the hardware abstraction layer i.e. an include header file with simple macros (sometimes not so simple) that abstract the hardware in use. Your typical bit manipulation operations go here. Then we have the api.h and the api.c files. Both of these together represent the application abstraction layer, where api.h is the include header file containing e.g. the various function prototypes and api.c is the file containing the actual implementation of those functions. Then we have the main.c file where the entire logic comes together. main.c references api.h which in turn references hal.h. This is a common way of separating the different layers of an application.

Then in each custom include header file, we have a construct such as this one:

#ifndef HAL_H_
#define HAL_H_

/*

  You code goes here

*/

#endif

This is what’s called an include safeguard or just an include guard and what it does is prevents the preporcessor from recursively including the same file multiple times thus avoiding compilation errors. This is also not just common but a good practice to start with and most IDEs will do that for you if you use the IDE’s templating features.

Bit flipping (also refered to as bit manipulation or bitwise operations) in microcontrollers is a topic on its own. There are plenty of resources online to look at or read through, so I will not spend too much time explaining it. Let’s take selecting a bit or creating a bit mask:

uint8_t bit_mask = 1 << 5;

bit_mask is of type uint8_t. That’s an unsigned 8 bit integer type. We create the variable and we assign the value of the operation left bit shift 1 for 5 places. So, we start with a value of 0b00000001 and bit shift the 1 five bit positions to the left, leaving us with a value of 0b00100000. And that’s our bit mask. We could accomplish the same by doing this instead (some do it this way, but I find the first approach more clear):

uint8_t bit_mask = 0x20;

Now imagine we have a variable called my_register, also of type uint8_t, and it’s current value is 0x4D or 0b01001101, and then we do this instruction:

my_register |= bit_mask;

This is equivalent to:

my_register = my_register | bit_mask;

… and the result is:

   0100 1101‬
OR 0010 0000
------------
‭=  0110 1101‬

If you compare the result with the initial value of my_register, you will see that only the bit on the 6th position (as in the bit mask) has been set and the rest of the bits unchanged. You might come across at this sometimes as friendly bit manipulation or friendly bit flipping.

Feel free to use the examples above to apply the same logic and work out an answer at what the other macros do.

Sometimes, you may set multiple bits at once (a.k.a. a compound statement). Take this line of code from the api.h file:

#define _set_outputs_port_b() _set_bit( ( DDRB ), ( _select_bit(EN_PIN) | _select_bit(D0) | _select_bit(D1) | _select_bit(D2) | _select_bit(D3) ) )

I have created a macro with which I can set multiple bits of the PORTB register.

In api.h and api.c I have defined a function to display the digits from 1 to 9:

void display_digit(digit_t digit);

digit_t is an enum type:

enum digits
{
	ZERO,
	ONE,
	TWO,
	THREE,
	FOUR,
	FIVE,
	SIX,
	SEVEN,
	EIGHT,
	NINE
};

typedef enum digits digit_t;

display_digit() is very straightforward. You pass the digit you wish to display – and voila! Anything different passed to display_digit – 0 is displayed by default!

Then inside main() in main.c just before the while(1) loop (the setup section of the program) we set the inputs and outputs, we set one of the timers of the ATmega328P and enable the interrupts (which we use to detect short or long switch button presses:

_set_outputs_port_b();
_set_inputs_port_c();

TCCR0A |= 1 << WGM01;  // Timer 0 mode 2 - CTC
TCCR0B |= 1 << CS02;   // Set pre-scaler to 256
OCR0A = 125;           // Number of ticks in Output Compare Register for a delay of 2ms
TIMSK0 |= 1 << OCIE1A; // Trigger interrupt when counter(TCNT0) >= OCR0A

sei();

Then just before we enter the endless loop, we check if the button has been pressed – if so, the micro displays the digits 1 to 9 manually – each time the user presses the button. Otherwise, it will display digits 1 to 9 continuously in a loop with a 1 second delay in between. When in manual mode, if the user presses the button for too long (keeps it pressed), the digit currently on display will remain displayed.

One thing I would’ve liked to implement was a debounce functionality (in software), but I decided to do this at hardware level for the final product.

Last but not least, I would like to mention few words on how I check for a short vs. a long button press… I already mentioned I accomplish this using one of the ATmega328P timers. Each time the counter counts up 2 milliseconds, the ISR(TIMER0_COMPA_vect) routines fires. Using the timer variable, we can count in multiples of 2ms. We can even setup the timer to count up and compare when 1ms has been reached. But for this application, 2ms is more than enough I think. Of course, there are other ways to accomplish the same I’m sure.

My next milestone, is to finish up a complete circuit with all nine 7 segment LED displays and wire wrap it – so I can put the entire software (including the I2C functionality) and then – order the PCBs from a supplier.

That’s it for now.