Further testing the μController Display Module

In the last post, I was able to get the μController version of the display module working with a basic test routine that proved that 1. the controller could be programmed and 2. the display was being driven correctly. The next stage is to flesh this out to replicate the MAX2771 API. The purpose here is not to simply create a duplicate of the MAX2771 which does a great job on its own. This approach means that I can re-use the code I created for the MAX2771 display module project. The additional goal of this small project was to gain experience in creating a SPI slave! Interfacing with the MAX2771 was simple since that is already a SPI slave unit. All I needed was to just create the master.

Connecting things together

I still have things set up from the previous tests so it was just a matter of connecting things back up again. Though this time I had two units to program instead of just one. I still have the main controller board for the clock to complete so I used the myAVR Dev board as a provisional main controller. Luckly, the myAVR dev board comes with its own programmer. I had to program this via the myAVR ProgTool since the BASCOM-AVR IDE was in use with programming the display module via the AVRISP mkII. It sounds more awkward than what it turned out to be. The part that was awkward was that the AVRISP mkII had trouble programming the display module while the SPI connection was still made to the myAVR dev board (my provisional mainboard). I had to disconnect the /SS, MOSI and CLK from the display module before being able to program. This would be a point of research to work out what is involved to be able to program the display module while still connected!

μControlled Display Driver Integration Test
μControlled Display Driver Integration Test

Once the infrastructure of programmers and connections was sorted, it was just then a matter of piecing together the code to enable the slave unit to start servicing the requests of the master i.e to start displaying the time. With the Real Time Clock still connected (via an I2C bus) I had a neat source of test data.

Getting thing up and running was rewarded with a red blinking LED on the dev board. This was my signal that the communication with the Real Time Clock was functioning. The next was the display module itself.To keep things simple,  I am opting to use the basic BASCOM constructs for SPI. The logic analyzer was useful for seeing that the SPI messages were being sent. Though my particular logic analyzer only shows that there was activity. It lacks the sophistication of being able to see exactly what was being sent. Thankfully, I have been able to get things working without that level of sophistication.


Creating the Slave

Dim Rbit As Bit
Config Pinb.4 = Output                    ' MISO
Config Spi = Hard , Interrupt = On , Data Order = Msb , 
       Master = No , Polarity = Low , Phase = 0 , Clockrate = 128

On Spi Spi_isr Nosave

Enable Timer0
Enable Timer1
Enable Interrupts

The snippet above shows how the slave was defined. Basically I used the same construct in the master. The “Master = yes” property is the only difference. Once data is available for the slave to read, the spi_isr interrupt routine is executed. Here I just grab the data into a Regisiter array. The API I was wanting to implement was to read a two byte command i.e. a register address and then a value. I was not sure on how this is to be done since BASCOM-AVR documentation only really talks about receiving one byte at a time for slave. In comparison, sending multiple bytes from the master is not difficult at all.

   PUSH r24    ; save used register
   IN r24, SREG; save SREG
   PUSH r24
   Set Rbit                         ' we received something
      If Spi_idx = 0 Then
         Idx = SPDR
         Set Spi_idx
         Register(idx) = SPDR
         Reset Spi_idx
      End If
   POP r24
   !OUT SREG, r24 ; restore sreg
   POP r24       ; and the used register

Here I had a variable to track which element of the command was being received spi_idx. When reset, then the first value from the master is expected and therefore that value is recorded as an index value i.e the register address. When spi_idx is set, then it is expected that the value from the master is the register value and that is then directly recorded in the Register array.

With the register address and value for the register obtained from the master, then it was a matter of interpreting the newly arrived information. I implemented this using a case construct.

   If Spi_idx = 0 Then
      Select Case Idx
         Case Hours_10:
            Digits(1) = Register(idx)
         Case Hours_unit:
             Digits(2) = Register(idx)
         Case Minutes_10:
            Digits(3) = Register(idx)
         Case Minutes_unit:
            Digits(4) = Register(idx)
         Case Colon:
            Digits(5) = Register(idx)
         Case Indicators:
            Digits(6) = Register(idx)
         ' Case Intensity:
         ' Case Power_mode:
         Case Digit_blink:
             Blink_mask = Register(idx)
         ' Case Blink_rate:
         Case Test_mode:
            If Register(idx) = Enabled Then
               Digits(1) = 8
               Digits(2) = 8
               Digits(3) = 8
               Digits(4) = 8
               Digits(5) = 8
               Digits(6) = 8
               Digits(1) = 0
               Digits(2) = 0
               Digits(3) = 0
               Digits(4) = 0
               Digits(5) = 0
               Digits(6) = 0
            End If
         Case Else
      End Select
      End If
   End If

The idea here is to simply  react if there is a new value arrived. Based on the value of idx (the register address) to then assign the value from the register to a variable that will result in the desired behaviour. i.e. setting a digit value etc.

One feature I have implemented that is not part of the MAX2771 API is a Blink feature. i.e. to enable a digit or colon to blink. I managed this with a mask with one bit for each element of the display. This seems to work quite well.

   Timer0 = Timer0_count
   Incr Position
   If Position > 5 Then
      Position = 0
   End If

   Digit = Position + 1
   PORTD = &H0A          ' Set all segments off
   PORTC = Makebcd(position)

   Blink_test = &B00000001
   Shift Blink_test , Left , Position
   Blink_test = Blink_test And Blink_mask
   If blink_flag = 1 and blink_test > 0 Then
      Value = Digit_off
      Value = Digits(digit)
   End If
   PORTD = Value

Next Steps

The μController version of the display module is working, I have to admit, in principle. There is still a bit more work to do to clean up its behaviour and possibly implement more tests and features i.e. the indicators on the top and bottom left of the display.

The actual main board still needs to be done. This will be presented in future posts. There are some considerations to be done with that in terms of its physical size for the existing casing. Another task that is outstanding is the interfacing of the Real Time Clock with SPI rather than I2C. I have been using I2C since that is what I used on the very first trial. My goal is to have a multiple slave SPI implementation. This too can be prototyped with my current set up before finalising on the actual main controller board.

The advantage I have with the current approach is that the firmware for the main board is progressively being done with the testing of the modules. It is just missing the implementation of the various push buttons. Even this can be implemented in the myAVR Development board.


Alternate Display Driver

The purpose of this entry is to describe the assembly,  programming and testing of the alternate Clock Display Module – whereby instead of a dedicated display driver chip, the module will be controlled by its own micro controller. This was created under a separate GitHub branch as UCtl_small. After the relative ease of getting the MAX7221 version of the display module working, it is easy to think that this version to go just as smoothly. This article will describe more about the approach that will be taken and the planning for the final API. Subsequent posts will document the execution and results.


In one of the latest modules of Contextual Electronics, the services of OSH-Stensils was introduced with the purpose of trying out reflow in an electric frying pan. I don’t consider myself at the stage to try it out (my work bench is just not big enough for another appliance), but the idea of using the stencil had me intrigued. Not long after submitting the board for fabrication, I also ordered the associated stencils, along with a jig to hold the work in place. The assembly went very smoothly! The stencils are a great help.


They also helped to demonstrate that you don’t need a lot of paste at all to get a good soldering job. The trickiest was the transistors. They would have to be the smallest parts I have soldered to date. I am very grateful to have been introduced to the stencil idea before attempting this board.



The board is now assembled and I am very satisfied with the result, now comes the testing. I think the is the most exciting oart of the development. All the previous work of sifting through data sheets and trawling through vendor sites looking for parts, creating the schematic and working through the layout puzzle all comes down to this monent! Will it work? Or will it sit there is stoney silence and do nothing? Now that I am at this point, I realise that this board, although functionally the same as the MAX7221 based display module, is significantly different in the way it is to be “brought up”. The MAX7221 based board has given me confidence with SPI interfacing. However, this board is still dumb. The processor has not yet been programmed. So there is my first challenge – onboard programming. In the design, I included a pin header for the programmer connector.

One Step at a Time

I have come to realise that I will need some type of systematic approach to the problem. In a similar vein with the MAX7221 based board, starting with a “Heartbeat” and building on that, I will need to start small and gain confidence that a section is working before proceeding with the next. I figure, I can tackle this with the following areas:

  • Programming interface
  • SPI Slave
  • Display Module API

Programming Interface

In this section, I need to prove I have everything connected correctly in oder to achieve processor start up plus inboard programming. To confirm this, as much as it seems extraneous work, I will bread-board a processor with the pin header and a LED just to conform the “Hello World” scenario. If this does not work on the bread-board with the same configuration of the display module, then I know I have something fundamentally wrong. “Hey, wait a minute”, I hear you say. “Shouldn’t this have already been done?”. Well, yes, it was but a while ago and I to just try it again to double checkand then I have a model that I can pull around, if required. Once I have the confidence with respect to the set up, then I can transfer the “Hello World” excercise to the display module board. It does not matter that it will not be displaying any digits, the main thins is that it blinks a LED or segment – any or perhaps a sequence. The idea here is that this will prove that the basic infrastructure is working. The programming interface and the IO lines to the LEDs.

SPI Interface

The next step after asserting the basic infrastructure is to start bullding the firmware for the display module proper. This means setting up the display module as a Slave.  The first response should be at least to react to a specific register-value pair. This could be considered the test mode for the module (see the API specification below) and could even reuse the test code in the previous test.

Display Module API

Once I have successfully reached this stage, everything is in place to start defining the actual API for the module.  An overview of the possible commands is shown in the following table. Some of these features are nice-to-have (*) and will be subject to further development once the basic features are implemented.

Basically it will work using eight bit register-value pairs. The resister will specify an operation or feature and the value will configure that operation or feature. Some of these operations and features will be mutually exclusive, so a priority will need to be fleshed out. Further refinement of the descriptions will be subject of further posts as the features mature.

Register Name Note
0x01 Hour 10s  The 10s digit for the hour part of the time

BCD value from 0-2

0x02 Hour units  The units digit for the hour part of the time

BCD value from 0-9

0x03 Minuts 10s  The 10s digit for the minutes part of the time

BCD value from 0-5

0x04 Minutes units  The units digit for the minutes part of the time.

BCD value from 0-9

0x05 Colon  The colon (between the hours and minutes)

0x00 – Colon Disabled
0x01 – Colon Enabled

0x06 Indicators  The indicator lamps that are featured on the display

0x00 – All indicators OFF
0x01 – Top indicator ON
0x02 – Bottom indicator ON
0x03 – Top and Bottom indicator ON

0x0A Intensity*  Controls the intensity of the display

0x00 –

0x0C Power mode  Enables the display unit

0x00 – Display unit OFF
0x01 – Display unit ON

0x0D Digit Blink*  Defines which digit or display element should blink. The data value of the register is a set of bit-wise flags for each element. OR-ing the various bit enables the digits to be selectively enabled for blocking, or not.

0x00 – No blink
0x02 – Digit 1
0x04 – Digit 2
0x08 – Digit 3
0x10 – Digit 4
0x11 – Colon
0x12 – Indicators
0x1F – All

0x0E Blink Rate*  Controls the blink rate of the digit

0x00 –

0x0F Test Lamp test

0x00 – Lamp test off
0x01 – All segments on
0x02 – Sequence run*

Serial Data Format

D15 D14 D13 D12 D11 D10 D9 D8   D7 D6 D5 D4 D3 D2 D1 D0
MSB     ADDRESS            LSB  MSB      DATA        LSB

Next Steps

The next steps are already described in the body of this post. Needless to say the board needs to be powered up and programmed with the basic “Hello World” example to prove the connections and basic infrastructure.

MAX7221 Driven Display Module

It is now time to put some theory into action. This entry will talk about the implementation of the clock display module driven by the MAX7221. This chip was selected for the first version of the display module as it provided a one-stop solution for driving seven segment displays. It also SPI compatible and provided some additional features like program selectable intensity.
The assembly of the board went without any issues. In a previous blog, I had calculated a Rset as 50KΩ in order to limit the segment current to 10mA. In the end, I only used what I had on-hand, so I used a 56KΩ. I was unsure about this and prepared to change the resistor if the intensity was not adequate.


Once assembled, the display module was connected to the myAVR development board that I am using. I used ATMega88-PinOutthe pre-defined pins for the SPI interface. From a previous test, the Real Time Clock (RTC) was connected with the I²C interface, also using the pre-defined pins. To keep thing simple for this test, I left this the way it was. Even though, in the final implementation, i will be using SPI for both the RTC and the display module.

The RTC is driven by 3.3V and therefore needs a couple of level shifters (shown in the photo) to convert the 5V signal to the 3.3V. I already had those on-hand from previous trials.



With everything connected and double checked, it was time to start creating some code. This would  be my first attempt at programming the SPI so I was a bit unsure how it would go. The RTC and I²C was a known quantity so I decided to start with that first and create a heart-beat pulse. This would prove that the program is being written to the micro controller correctly and that the basic infrastructure was in place.

This was achieved by configuring the Pin Change Interrupt (PCINT) 16 and 17 for the one second pulse and the DFC complete signal respectively. The one second pulse enables the micro controller concentrate on orchestrating the information between the RTC and display module and does not have to implement any internal timers. This also means I don’t need any special crystal circuitry for additional accuracy. PCINT16 and PCINT17 are part of the Pin Change Mask Register 2 (PCMSK2) at bits 0 and 1 of the mask register. The set up for these is shown in BASCOM below. In this case, PORTB.0 is configured to be connected to the LED to provide the heart-beat.

On Pcint2 Pcint2_isr
Pcmsk2.0 = 1 ' PCINT16 - 1 Second pulse
Pcmsk2.1 = 1 ' PCINT17 - DCF message

Enable Pcint2
Enable Interrupts


Config Pind.0 = Input
Second_interrupt Alias Pind.0
Dim New_second As Bit

Config Pind.1 = Input
Dcf_interrupt Alias Pind.1

Config Portb.0 = Output
Heartbeat Alias Portb.0


Do ' Main control loop


 If Second_interrupt = 0 Then New_second = 1
 If Dcf_interrupt = 0 Then Time_dcf = 0

For the full source, see the GitHub repo for simpleCheck.bas

The MAX7221 was programmed first by setting up the SPI interface, using the config spi instruction. Before the main control loop, the initialisation subroutine is called. This sets the initial parameters for the MAX7221 and also runs a two second lamp check. The MAX7221 can drive up to 8 digits. I am technically only using four digits, so in order to avoid any flicker on the display, the duty cycle needs to be configured not drive the digits that are not needed. In actual fact,  from the original design, I am using 6 digits. The segments A and B of the fifth and sixth digits are used for the colon and indicators respectively.

Config Spi = Hard , Master = Yes , Data_order = Msb

' Display

Const Intensity_register = &H0A
Const Intensity_max = &H0F
Const Intensity_med = &H07
Const Intensity_min = &H00

Const Shutdown_register = &H0C
Const Normal_mode = &H01

Const Display_test_register = &H0F
Const Display_test_on = &H01
Const Display_test_off = &H00

' Scan Lmit 0xXB
Const Scan_limit_addr = &H0B
Const Scan_limit_value = &H05 ' Display digits 0 1 2 3 4 5

' Decode mode 0xX9
Const Decode_mode_addr = &H09
Const Decode_mode_value = &H0F ' Code B decode for digits 3-0 No decode for digits 7-4

Const Digit1_addr = &H01
Const Digit2_addr = &H02
Const Digit3_addr = &H03
Const Digit4_addr = &H04
Const Colon_addr = &H05
Const Signal_addr = &H06

Dim Spi_data(2) As Byte


Gosub Init_display


' Subroutines =================================================================


' Put the MAX7221 into normal operation mode.
   Spi_data(1) = Shutdown_register
   Spi_data(2) = Normal_mode
   Spiout Spi_data(1) , 2

' Run the test mode for 2 seconds.
   Spi_data(1) = Display_test_register
   Spi_data(2) = Display_test_on
   Spiout Spi_data(1) , 2

   Wait 2

   Spi_data(1) = Display_test_register
   Spi_data(2) = Display_test_off
   Spiout Spi_data(1) , 2

' Set the display intensity to maximum
   Spi_data(1) = Intensity_register
   Spi_data(2) = Intensity_max
   Spiout Spi_data(1) , 2

' Set the scan limit i.e. the number of digits to support
   Spi_data(1) = Scan_limit_addr
   Spi_data(2) = Scan_limit_value
   Spiout Spi_data(1) , 2

' Set the decode level - which digits to be treated as
' numbers and which will be custom driven
   Spi_data(1) = Decode_mode_addr
   Spi_data(2) = Decode_mode_value
   Spiout Spi_data(1) , 2

' Turn off the indicators
   Spi_data(1) = Signal_addr
   Spi_data(2) = 0
   Spiout Spi_data(1) , 2

' Turn off the colon
   Spi_data(1) = Colon_addr
   Spi_data(2) = 0
   Spiout Spi_data(1) , 2


Setting the device into normal operation was the real trick. Before I did that, it remained in an off state. So by default, it is configured to be in a shutdown mode. I had mentioned I was concerned about the resistor and the intensity of the display. It turns out that the 56KΩ is more than adequate, in fact, I can imagine that in the final implementation, a lower intensity will be required.

With the heart-beat functioning and the correct initial values being displayed, then it was only a matter of waiting for the RTC to receive its DCF77 signal for the correct time to be displayed. This eventually occurred after a minute or so. Then I started with the enhancements such as driving the colon also from the heart-beat pulse.


In conclusion, since the assembly and testing went largely without any issues, I can only sing the praises for the MAX7221. Though it is quite pricey, it certainly saves time and space for any other type of display driver. I am now curious about the next driver I intend to trial where I have to create my own firmware.

One extra thought I have is regarding the layout of the board. For this board the SPI pin header is in the bottom right hand corner. For the next board I will be working on, for no good reason, the SPI pin header is in the middle. It would have made more sense to make them compatible so that I could also be working on the main board and then interchange the display modules as needed. Hind-sight is a wonderful thing.

Ohm my!

I have to take a break from the circuits and layouts and go back and verify a couple of calculations. I am referring to the Rset for the MAX7221. I need to be sure that this is going to be correct in relation to the 7-Segment units chosen – and also the discrete LEDs for the indicators.

  • 7-Segment: NKR161B
    • Vf = 1.7V per segment
    • Maximum forward current = 30mA
  • LED:
    • Vf = 2.0
    • 20mA Test current

For Rset,



In theory, the MAX7221 should be able to drive the 7-Segment displays directly using a 50KΩ resistor. It also looks like the discrete LEDs for the indicators can be accommodated. What the impact will be in real terms of brightness and evenness of illumination, I will wait to see it for my self.

Getting Connected

The layout is now done and made the rather gutsy decision to send it off to OSHPark for fabrication. In hindsight, I could have done this cheaper in that I actually opted for a 4 layer board with the ground and power on the inner layers just to get them out of the way. I did try a 2 layer board but with the busses running for the segment and digit lines, it was very tight for space especially around the driver chip.

I had to create the footprints for both the 7-Segment units and the MAX7221. The MAX7221 has probably the highest risk of fitting since the package description was anything but clear. It was stated as a 24-SOIC Wide with about five variations on the length – none of which can be determined from the part numbers. The part number that I am able to readily obtains is MAX7221EWG or MAX7221CWG. For this, I have selected the set of dimensions that would give me the largest pad size in the assumption this will simplify assembly. The length of the package itself does not seem to impact the placing of the pads, so I am confident, it should be OK.

The ordered boards are shown below. A couple of points, again learned from my experiences with Contextual Electronics – Label the Pin-Headers! it is annoying to have to look back on the schematic each time I want to connect up a board. The other is position the buffer resistors for the LEDs sensibility – As I mentioned in the last post, there could be a good reason to swap out the zero Ohm resistor with something more appropriate. In order to avoid damage to other parts i.e. the plastic of the 7-Segment displays, I have positioned these on either away from the 7-Segment displays or on the Fron-side (the 7-Segment displays are on the back side).



What’s next?

It is easy to rest on my laurels and think, “OK, I have three or so weeks before I get the boards back, I can concentrate my efforts on the Contextual Electronics course work”. However, there are other things to do

  • Verify the calculations for the Rset
  • Verify the specifications of the discrete LEDs and determine if the zero Ohm resistor is all I need.
  • Consider working on the other branch for the layout of the larger displays.
  • Consider re-working the layout for a 2-layer board to reduce the cost.
  • Start work on the Controller based display module.

The next post will reveal what I have decided to work on.

Branching out

I mentioned in the last post that I will be trying out the display modules. I found it easier to break away from the original TfaC Git Hub project and create a new one dedicated to the research and development for the display module. The new one is simply ClockDisplayModule.

The first consideration I needed was what 7-Segment displays. The original have a digit height of about 19mm. It is not possible to obtain this with the resources I have. So there is my first compromise. A near fit is about 20.32mm or a 15.24mm. In order to accommodate the usage of either of these, since neither is ruled out, I decided to create two branches on the ClockDisplayModule: largeDisplay and smallDisplay for the two 7-Segment sizes respectively.

driverThe first thing I needed was the circuit diagram for connecting the MAX7221.

I decided to model most of the connections as global labels to avoid clutter on the diagram. It also meant I could separate out the various components of the module in terms of the array of 7-Segment displays, the indicator LEDs and the driver itself.

The data sheet for the driver recommends an electrolytic 10μF and a 0.1μF near the V+. The rest is pretty much as per the application notes on the data sheet. At the moment I have selected a 25KΩ for the Rset which controls the current to the LED segments. This may be also subject to review based on the final 7-Segment selected. I will be providing the working for the final calculation.


The 7-Segment displays have been modelled using the bus feature of KiCad. The common cathode of the displays goes back to the “digit select” on the MAX7221.

The indicators for the display will be implemented using discrete LEDs rather than any decimal point on the 7-segment display units. Mainly because they are not in a position that I consider useful.

I have not seen the MAX7221 driving discrete LEDs along with any 7-Segment display, but I am keen to try this out. I prefer to use discrete LEDs rather than any decimal point on the 7-Segment units as a matter of position. I believe that I will have to consider the ratings of the LEDs and possibly match them with the display segments to avoid any issues loading the MAX7221 inappropriately.

indicatorsMy participation in the Contextual Electronics course has given me the tip to place some zero Ohm resistors in series with the indicator LEDs. The MAX7221 is capable of limiting the current to the LEDs it is driving through the Rset (R101). However if I can not match the indicator LEDs correctly, I at least have the option of swapping the zero Ohm resistors with something more appropriate.

What’s Next?

The next step will be to lay this all out onto a board. I already have the measurements I will use – that is the same size as the existing display panel – 3.5″x1.3″.

Back to the Drawing Board

Well, not exactly back to the drawing board for everything… What has developed since doing the actual schematic is that the display module was treated as a black box. Now it is time to open this up and understand how this is to work. The beauty of this approach is that not knowing anything about the display module has not held me up on the other, simpler aspects of the project. This module is possibly going to be the most challenging. There are several dynamic elements to it and it is also going to be the module that will draw the most current. So far I have indicated that this module will be supplied by Vbb i.e. +5V and will be interfaced with the controller via a SPI bus.

The first challenge is which direction to take this. I find myself at a bit of a cross roads. Searching the internet for a suitable example as a starting point, I see there are already 7-segment display driver from Maxim i.e. Display module - MaximMAX7221. The advantage here is that it provides many features that make the implementation of a clock display relatively simple.  I have quickly put together the block diagram that shows the intended implementation. It seems I could use the fourth and fifth digits to drive the colon and auto-time and alarm enabled indicators. The implications on the driving current need to be ratified.

The down side is the cost and the power consumption. It would mean rethinking my use of the MCP1703T-5002E/CB as the main regulator since this is limited to 250mA only. In reality, for practical purposes, even 250mA is to high for an appliance that is expected to be running 24×7 such as a digital alarm clock.

The alternative to using the display driver is to implement this myself. The advantage there is I am free to introduce what behaviour I need for the module to work within this project. The disadvantage is additional complexity in firmware updates. There will be two micro-controllers to contend with. At this stage, I have not done any calculations to see if there would be a saving in cost or power drawn by going the home-made route.

Display module - Controller

The block diagram for the micro controller based display module is very similar. The micro controller is used to replace the behaviour of the display driver chip. Although an ATMega88 is depicted here, any suitable i.e. low powered micro controller would suffice. As long as the duty cycle on the LED segments and indicator lamps is enough to provide a clear display.

Where to now?

Since I am at a cross road, the best way forward is to try things out. This is probably not good SysML practice since the design is not actually complete. But prototyping must be considered a part of design. I don’t have enough information to make a sensible decision as to the direction. The best way forward is to implement a prototype of the two possibilities and make the best choice from there. In this case I will be able to measure physical performance and power consumption and not relay on theoretical calculations.