4 Digit, 7 Segment Display
Thursday, May 17th, 2012In early May I started working on a module to display a 4 digit number on a 7 segment LED display. I developed this as far as a working proof of concept developing the hardware and software necessary to prove that the module was feasible. At this point I discovered that development was ongoing by two other teams and decided to stop development at this point taking what I had learnt onboard and move on to other projects.
This article presents the work so far as this is a working project and demonstrates some of the principles used in the STM8S articles written so far this month. Namely:
- GPIO output
- Timers
As already stated, this project is working but incomplete and there is no intention to take the hardware further than the proof of concept as presented here.
Hardware Design
The system will use four 7-segment LED modules in place of a single 4 digit, 7 segment module. The hardware design requires the following circuit components:
- Microprocessor controller
- Power supply to the 7-segment display modules
- Decoding for the 7-segments
The software will use multiplexing to allow one chip to be used to control the four 7-segment modules. The basic process is as follows:
- Turn power to all segments off
- Send the data for a digit to the 74LS47 chip
- Turn on the power for the digit
- Wait a short time
- Move on to the next digit and go back to start
This will give the illusion that all of the LEDs are powered all of the time.
Microcontroller
The microcontroller chosen was the STM8S003 as this is supported by the Netduino team for designers who wish to create their own modules. This chip has several free development environments available and the IAR environment has been used in this blog to document several small projects using this family of chips.
The project as it stands requires the use of 9 pins from the STM8S. Four pins will be used for power control, one line per digit. A further four pins will be used to tell the 74LS47 which of the segments are to be lit. The final pin will be used to indicate if the digit zero is to be shown for a zero value. More on this in the decoding section below. The use of this number of pins makes this unsuitable as it stands for use as a Netduino GO! module. Further refinement is required to take this forward as a module.
7-segment Power Control
The LED power is controlled by using a PNP transistor (the 7-segments share a common anode). In this circuit the power is run through a single current limiting resistor. This will mean that numbers with few digits (for example, 1) will be displayed more brightly then those with a large number of segments (say 8). The final circuit would need to refined to have a current limiting resistor per segment or using a constant current driver. This component of the circuit looks like this:
The resistor R5 is connected to the output of the microcontroller and the output of R9 going through to the power pin on the 7-segment display.
Decoding the 7-segments
The decoding of the output from the microcontroller is performed by the 74LS47 chip. This is a dedicated decoder circuit for common anode 7-segment LED modules. The system takes a binary coded decimal number and then sinks the current from the appropriate segments of the display. The result is that the module will display the appropriate digit.
We will also take advantage of one final line, the RBI line. By turning this line on and off we can determine if the digit will display the digit 0 when the input to the decoder is zero. So why would we change this line? We we could fix the line so that a digit will always be displayed. So when the microcontroller wants to show 0 it will actually show either a blank display or 0000. The blank display is obviously not desirable as the user will not be able to determine if the display is showing 0 or if it is turned off. Showing four zeroes will give the user the correct information. A nicer solution is for the display to show a single zero when the microcontroller outputs 0.
The resulting decoder wiring looks something like the following:
Where pins A, B, C and D will output the binary version of the digit to be displayed.
Full Circuit
This PDF File contains the full circuit diagram for the proof of concept module. The fully assembled circuit looks like this:
Software
In order to test the circuit we will write a small program which will start at 0 and count to 9999 before returning to 0 and starting counting again.
The first thing we need to do is to initialise the system. The following code resets the system clock source, configures the GPIO lines and then starts the timer which contain the code which will perform the multiplexing.
//
// Initialise the clock
//
CLK_DeInit();
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1); // CPU Prescaler = 1.
CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // Prescaler = 1, 16 MHz.
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, // Automatically switch
CLK_SOURCE_HSE, // Switch to internal timer.
DISABLE, // Disable the clock switch interrupt.
CLK_CURRENTCLOCKSTATE_DISABLE); // Disable the previous clock.
//
// Initialise GPIOs
//
GPIO_DeInit(GPIOC);
GPIO_Init(DIGIT0_BI_PORT, DIGIT0_BI_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT1_BI_PORT, DIGIT1_BI_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT2_BI_PORT, DIGIT2_BI_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT3_BI_PORT, DIGIT3_BI_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT_A0_PORT, DIGIT_A0_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT_A1_PORT, DIGIT_A1_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT_A2_PORT, DIGIT_A2_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(DIGIT_A3_PORT, DIGIT_A3_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init(RBI_PORT, RBI_PIN, GPIO_MODE_OUT_PP_LOW_FAST);
//
// Setup timer 2 to interrupt every 2048 clock pulses.
//
TIM2_DeInit();
TIM2_TimeBaseInit(TIM2_PRESCALER_2048, // Interrupt every 2048 clock pulses.
1); // Period is one.
TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE); // Enable the overflow interrupt.
TIM2_Cmd(ENABLE);
The next thing we need to do is to look at displaying a digit. To do this we will turn off all of the digits and then convert the digit into binary outputting the value on the four pins connected to the 74LS47 decoder.
GPIO_WriteHigh(DIGIT0_BI_PORT, DIGIT0_BI_PIN);
GPIO_WriteHigh(DIGIT1_BI_PORT, DIGIT1_BI_PIN);
GPIO_WriteHigh(DIGIT2_BI_PORT, DIGIT2_BI_PIN);
GPIO_WriteHigh(DIGIT3_BI_PORT, DIGIT3_BI_PIN);
if (digit & 0x01)
{
GPIO_WriteHigh(DIGIT_A0_PORT, DIGIT_A0_PIN);
}
else
{
GPIO_WriteLow(DIGIT_A0_PORT, DIGIT_A0_PIN);
}
if (digit & 0x02)
{
GPIO_WriteHigh(DIGIT_A1_PORT, DIGIT_A1_PIN);
}
else
{
GPIO_WriteLow(DIGIT_A1_PORT, DIGIT_A1_PIN);
}
if (digit & 0x04)
{
GPIO_WriteHigh(DIGIT_A2_PORT, DIGIT_A2_PIN);
}
else
{
GPIO_WriteLow(DIGIT_A2_PORT, DIGIT_A2_PIN);
}
if (digit & 0x08)
{
GPIO_WriteHigh(DIGIT_A3_PORT, DIGIT_A3_PIN);
}
else
{
GPIO_WriteLow(DIGIT_A3_PORT, DIGIT_A3_PIN);
}
GPIO_WriteLow(port, pin);
The final statement will turn on the PNP transistor connected to the selected digit by connecting the base of the transistor to ground.
The final piece of code we need (before the main program loop) is the interrupt handler for the timer. This will turn off all of the power to the LED modules and then work out which digit to show and write the data to the 74LS47 decoder. This interrupt service routine can be found in the file stm8s_it.c. See previous STM8S post on interrupts for further information. Look for the following code in this file:
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13)
{
}
And replace with the following code:
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13)
{
if (_currentDigit == 0)
{
DisplayDigit(_displayValue[0], DIGIT0_BI_PORT, DIGIT0_BI_PIN);
GPIO_WriteHigh(RBI_PORT, RBI_PIN);
_currentDigit++;
}
else
{
if (_currentDigit == 1)
{
DisplayDigit(_displayValue[1], DIGIT1_BI_PORT, DIGIT1_BI_PIN);
if ((_displayValue[2] != 0) || (_displayValue[3] != 0))
{
GPIO_WriteHigh(RBI_PORT, RBI_PIN);
}
else
{
GPIO_WriteLow(RBI_PORT, RBI_PIN);
}
_currentDigit++;
}
else
{
if (_currentDigit == 2)
{
DisplayDigit(_displayValue[2], DIGIT2_BI_PORT, DIGIT2_BI_PIN);
if (_displayValue[3] != 0)
{
GPIO_WriteHigh(RBI_PORT, RBI_PIN);
}
else
{
GPIO_WriteLow(RBI_PORT, RBI_PIN);
}
_currentDigit++;
}
else
{
DisplayDigit(_displayValue[3], DIGIT3_BI_PORT, DIGIT3_BI_PIN);
GPIO_WriteLow(RBI_PORT, RBI_PIN);
_currentDigit = 0;
}
}
}
TIM2->SR1 = (uint8_t) (~(uint8_t) TIM2_IT_UPDATE); // Clear the interrupt.
}
One interesting thing to look at is the setting of the RBI_PORT. Note that this is set to high for the first digit (i.e. rightmost digit) and low for all of the other digits. This is the piece of magic which ensure that we always display a digit for zero in the rightmost digit but only display a value for no zero digits on the remaining three digits.
If this were to be taken forward then the code above would need to be optimised to ensure that the ISR completed in the shortest time possible.
Now we have all of the elements in place we can start to write our main program loop. The code will loop from 0 to 9999 displaying each digit and the restarting the count. The individual digits will be put into an array of four integers, one for each LED in the display. This gives us the following code:
enableInterrupts(); // Make sure interrupts are enabled.
int value = 0;
while (1)
{
_displayValue[0] = value % 10;
_displayValue[1] = (value / 10) % 10;
_displayValue[2] = (value / 100) % 10;
_displayValue[3] = (value / 1000) % 10;
if (value == 9999)
{
value = 0;
}
else
{
value++;
}
//
// Now delay otherwise we'll be counting too fast for anything
// to appear on the display.
//
for (int outerDelay = 0; outerDelay < 5; outerDelay++)
{
for (int delay = 0; delay < 0xffff; delay++)
{
nop();
}
}
}
The full source code for this project can be downloaded here.
Conclusion
This was an interesting project to put together and took about a day to work out and get everything working. To take this further would take a lot more work, many times the amount already put into the project. As two other groups are working on similar ideas I have decided to archive this for now and move on to other projects.