Wrapping up the Alarm Clock Project

It has been a long while since the last post. Amongst other things, the clock has been a work-in-progress. Each time I made some progress, I found some new reason to create a revision. The initial revisions where based on issues and oversights. I am finding that as the project matures, the nature of the revisions are changing. Earlier on I was focused on just seeing things working i.e. how to program the micro with the ISP, how to get the modules to communicate. The earlier boards were either bound to the original dev-board, bread board or both. Now, I have the display module and the main-board untethered from the bread board and they are happily working with each other. Each step of the way I learn something new and those earlier issues are no longer the obstacles that they were. So much so that the problems and challenges I face now are more functional and mechanical.

I now have a basic board layout for the controller and the display that I am happy to progress with. These are certainly not the cheapest options, but as I may have mentioned before, they offer a way for me to learn about and demonstrate inter processor communication as well as dealing with multiple voltages in the one application. My focus now was to get the job finished. I see two basic challenges. 1. Getting everything into the case and 2. Getting the alarm working!

Low Profile 7-Segment Display

img_0268e

The seven segment displays I had been using would not allow me to mount the display in the case. It was time to search for a better solution. I discovered some thin profile seven segment displays. They are perfect for the job, though they are a bit tricky to solder since they do not have any leads. I worked around that with a piece of lead wire inserted in two of the holes in my board. This enabled my to apply light pressure on the display in the direction of the leads to hold them firm in position for soldering. Then allowed all the displays to be identical.

Alarm

The alarm buzzer then became my biggest challenge and something new to learn about.The clock originally had a Piezo buzzer. Since the goal is to restore the clock, I was hoping to use a Piezo again. However the results from testing were not what I had expected. It was really difficult to get any sort of appreciable sound. Admittedly, the simple Piezo need to be properly mounted in order to resonate correctly. This I could not really achieve with my test set-up.

Testing the Piezo and looking generally at buzzers was opening up a new avenue of concepts to learn. I found the theory simple enough and meant I still had some design decisions to make. The first thing was to understand what direction to take. Since this was new territory, I saw no other avenue but to order some different types and just try them out. The basic criteria I set was capable of handling at least 5V and in around the 90dB range. I have to admit, I had no idea what 90dB was going to give me.

Description Manufacturer Part Number Digi-Key part Number
Zero-Peak Signal Buzzer Single Tone 2.67kHz Magnetic 0~5V 88dB @ 3V, 10cm TDK Corporation CSQG703BP 445-4832-1-ND
Zero-Peak Signal Buzzer Single Tone 2kHz Magnetic 2~4V 85dB @ 3V, 5cm CUI Inc. SDR08540M3-01 102-1286-ND
Zero-Peak Signal Buzzer Single Tone 2.4kHz Magnetic 3~8V 87dB @ 5V, 10cm CUI Inc. CT-1205-SMT-TR 102-1199-1-ND

In choosing, I had the choice to use a device that need no external driver or to drive the buzzer myself. I opted for the second to also learn how to integrate the PWM feature of the micro into the solution. In testing the buzzers, I could mount the CSQG703BP directly on my bread board. For the other two I created a (rough) test harness and used a Gabotronics Xminilab as my signal source – I don’t possess a bench signal generator yet.

img_0270e

The CSQG703BP tested OK but the tone was quite weak. I could not get a tone out of the SDR08540M3-01 at all but that could be related to the set-up I had. Although the  CT-1205-SMT-TR was quite bulky in comparison to the others, it delivered the best results.

I made the final revision to the board to take the CT-1205-SMT-TR on the underside over the place where the Piezo was originally. The case had a moulding that gave more than enough space for this part, so I was lucky there.

In “bringing up” the buzzer, I had to learn about PWM control and what that meant. I created a test program to only operate the buzzer and not worry about the whole clock functionality yet. In the end I thought it would be cute if the alarm would not simply make the traditional chirps but send  the message “WAKE UP” in Morse code.wakeup

Buzzer Alias Portb.1
Config Buzzer = Output
' CTC-Mode for ~ 2.5kHz
Config Timer1 = Timer , Compare A = Toggle , Prescale = 1 , Clear Timer = 1

Const Timer0_startvalue = 100                               ' 0.001 seconds
Config Timer0 = Timer , Prescale = 64
Timer0 = Timer0_startvalue
On Timer0 Timer0_isr
Enable Timer0
Start Timer0
Enable Interrupts

Heartbeat Alias Portc.3 ' LED indicator
Config Heartbeat = Output : Heartbeat = 0
Const Tone_pitch = 200

Dim Tone As Byte
Dim Tone_length As Integer
Dim Tone_idx As Byte
Dim Song(40) As Byte
Dim Songlen As Byte
Dim Song_finished As Bit

Const Base_length = 7

' Load the song - Songlen should be used to know the length afterwards.
Wait 5
Songlen = 0
Restore Melody
Do
   Read Tone
   If Tone <> &HFF Then
      Incr Songlen
      Song(songlen) = Tone
   End If
Loop Until Tone = &HFF
Tone_length = 1

' A wake up signal. Illuminate the LED for one second.
Set Heartbeat
Wait 1
Reset Heartbeat

' Play the "song" every 10 seconds
Do
   If Song_finished = 1 Then
      Wait 10
      Tone_idx = 0
      Reset Song_finished
   End If
Loop
End

Buzzer_on:
   Compare1a = Tone_pitch
   Tccr1a.6 = 1
Return

Buzzer_off:
   Tccr1a.6 = 0
Return

' ISR for timer 0. This will traverse the "melody" playing each tone, and pause
' in turn until the end of the song.
' The Melody is a series of data values. Since there is only one tone from the 
' buzzer, only the length is given. There is a base length and the tone length is
' a multiple of that. Basically this comes from Morse code that the dots are three
' units long and the dashes are seven. Anything greater than &H80 is considered 
' a pause.
Timer0_isr:
   Timer0 = Timer0_startvalue
   If Tone_length > 0 Then
      Decr Tone_length
      If Tone_length <= Base_length Then
         Gosub Buzzer_off
      End If
   Else
      Toggle Heartbeat
      If Tone_idx < Songlen Then          Incr Tone_idx                            ' Get the next tone          Tone = Song(tone_idx)          Tone_length = Tone And &H0F              ' Get the tone length          Tone_length = Base_length * Tone_length  ' Factor the tone buy the base length          Tone_length = Tone_length + Base_length  ' Build in a unit gap at the end          If Tone > &H80 Then
            Gosub Buzzer_off
         Else
            Gosub Buzzer_on
         End If
      Else
         Set Song_finished
      End If
   End If
Return

Melody:                                             ' Wake up - Morse code
Data &H01 , &H03 , &H03 , &H83 , &H01 , &H03 , &H83 , &H03 , &H01 , 
     &H03 , &H83 , &H01 , &H87 , &H01 , &H01 , &H03 , &H83 , &H01 , 
     &H03 , &H03 , &H01 , &HFF

Alarm Integration

It was easy enough to integrate the alarm-tone code into the main-board code since since the 16-bit Timer0 was not in use. I could configure the Timer0 for CTC-Mode (Clear Timer on Compare) to deliver the 2.5kHz which produced the best results. I did attempt other PWM configurations but the results were not what I was expecting or wanted.

Config Timer1 = Timer , Compare A = Toggle , Prescale = 1 , Clear Timer = 1
Const Tone_pitch = 200

In my original design, I had anticipated to utilise the alarm interrupt (address $E) that the RTC-DFC supports – or is meant to support. I actually found that this does not work. When the alarm is programmed, it can be seen to be fired since the indicator LED on the module will illuminate. However, the Alarm Interrupt line is not brought low and therefore not detected in my code. I have two modules to try this out on and neither would pull the interrupt line low – even after upgrading to the latest firmware.

After chatting with my tutor, Chris Gammell from Contextual Electronics, I came to realise the workaround. In addition to the Interrupt line, it is possible to detect the alarm in the interrupt status register. This works. So I modified the code not to rely on the interrupt line but to, instead, query the Interrupt Status Register and invoke the alarm if the alarm bit is set.

Read_dfc_interrupt_flag:
   Reset Dfc_ss
   Mosi(1) = &HCE
   Spiout Mosi(1) , 1
   Spiin Mosi(1) , 1
   Set Dfc_ss
   Interrupt_register = Mosi(1)

   If Alarm_disabled = 0 Then
      Temp = Interrupt_register And &B00000100
      If Temp = &B00000100 Then
         Set Alarm_fired
      End If
   End If
Return

Wrapping up

I was keen to get this project into a finished state. The enabling of the alarm was one of the last stages. All was needed then was to clean up the code a bit and implement an “alarm cancel”. There is a push-button on one of the front feet so that the alarm can be cancelled by tapping the top of the clock.

img_0274eSo here it is, the renovated clock. I utilised the BOM feature of KiCad to perform a cost analysis of the two modules. The Clock module comes to a BOM cost of 41.46€ (including the RTC-DFC from ELV) and the display module comes to a BOM cost of 35.14€. I have already mentioned that this implementation is a bit over the top in terms of components etc. but it is more about the learning exercise rather than making a cost effective clock and display module.

Just to recap on what I have learned in this exercise:

  • Circuit design
  • PCB layout
  • PCB Manufacturing
  • Assembly
    • Improved through-hole soldering
    • SMD
  • Programming through an ISP (to a development board and my own board)
  • Inter module programming using I²C and SPI
  • Trouble shooting using a logic analyser and oscilloscope

img_0275e

When I set out on this project, I anticipated to learn a bit about UML – SysML in particular. I did start out that way but with everything else I had to learn on the way, it sort of went by the way. However the block diagrams that I created ended up being an valuable reference of what I had intended the pin allocations to be. That is something will try to utilise more for the next project.

Addendum

Now that the clock has been running a week or so, I was thinking about how it is able to pick up the time signal better in some places than others. I was made think about this further when the ELV Journal showcased an external antenna for the RTC-DCF. I realised that all this time, I have been treating the DFC-RTF as a black box an literally taking it for granted. That approach certainly helped to get the job done. It was time to take a closer look. The RTC-DFC is a module in two parts. A DCF77 receiver module and a micro based RTC. It turns out that I really had a bit of luck with the positioning of the antenna on the main board. In certain locations the clock can (eventually) receive a signal, in others not at all. Looking at the data signal from the DCF77, I could clearly see the one second pulses when the antenna was about 5cm from the board. Bring the antenna closer and the signal is disrupted as the images below show.

It was not a light decision to take to add the external antenna to the clock as I had to make a slight modification to the case for the antenna cable. The performance of the clock as improved immensely. It now sits, as a trophy, on my Lab windowsill.

The Main Board

I created the main board in the same pattern as the original. That made for quite an expensive prototype as there is a lot of empty space. This size is needed to fit the tactile switches in their proper places. Now that I have Revision 2 up and running, a write up of what I have learned along the way is over due.

The assembly was not without issues. There were some lessons to learn with KiCad and circuit design and layout as I did make some mistakes but I was able to identify the issue and make some corrections. Here are some of the things I picked up along the way

Check the pin outs

This might sound like an obvious thing and it hind-sight, it was. Complacency crept in on more than one occasion and I did not verify the pin allocations between the datasheet, symbol and footprint for the linear regulator. As it was a three pinned device, I only needed to reset it and run a bridge to ground.

LEDs

I did not factor any indicator LEDs on the main board. While this is not a mistake, the addition of an LED to blink to show that something is happening is well worth the few seconds it takes to model it in the schematic and layout.

Don’t leave GPIO pins unconnected

This is just to say for any prototyping board, it would be worthwhile to expose any spare pins to headers, and or coupled with 0Ω resistors. When troubleshooting, you never know when you might need something. Of course in the production board, these would not be populated.

Getting Things up and Running

Heartbeat-Bodge-bOnce the power rails were reading the correct voltage, I connected in the RTC module. This fired up OK. I.e. Its LED lit up for a second. This is when I realised I was missing a heartbeat LED. I simply connected a piece of through hole lead to Port B0. This was the output that was configured as the heartbeat on the myAVR development board.

 

Using the procedure I used for the previous boards, I tested the connection between the PC and the AVRISP mkII using the Atmel Studio. If I could read the serial number of the micro, then I knew I was in good form.

SystemTest-01b
Main Board and Rev1 of the Display Module

I flashed the program I was using on the myAVR Dev board. Straight away the heartbeat started pulsing. Another good sign. The display did not react, or when it did, it was with garbage.

The issue with the display module as due to a mismatch between the main board and the display module. Since communication was OK to the RTC, I could assume that the SPI was largely OK. The obvious place the issue would be was with the actual signalling and or timing.

This issue has kept me occupied for some time. The biggest help was the Salea Logic Analyzer. To be able to capture the signals and have the digital values interpreted is a huge advantage. I could read the values being sent to the clock and display to ensure they were correct. I was able to tweak the code so that I could match the SPI specifications described on the RTC datasheet. I used this as a base to then configure the display module. Everything started to fall into place then and the display was more stable.

I found that to get the flexibility out of BASCOM, I needed to set up the Master i.e. the main board using software controlled SPI. Whereas, I could simply rely on the hardware SPI for the slave (display board).

Main Board SPI Setup

Fir the main board, the software controlled SPI was preferred since I could then use the SS = None property on the configuration. I then had to allocate my own chip-select lines for each of the RTC and the display module.

Config Spi = Soft , Din = Pinb.4 , Dout = Portb.3 , Ss = None , Clock = Portb.5 , Setup = 40 , Mode = 1

Display_ss Alias Portb.2
Config Display_ss = Output : Display_ss = 1

Dcf_ss Alias Portb.1
Config Dcf_ss = Output : Dcf_ss = 1

Spiinit

Display Board SPI setup

The key to the display board setup was to use the Master = no to ensure that this would be configured as a slave. All pin allocations were as per the datasheet for the micro controller.

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

Spiinit

On Spi Spi_isr                                              'Nosave

In principle, things were good. I know I can flash the display module and I know I can flash the main board. I also know that communication is happening between the main board and the RTC.However there is still a niggling problem between the controller board and the display unit. After a while, the data seems to get out of sync and the display starts to display wrong values. SignalToDisplay Examining the SPI signals, I could be satisfied that the data presented and received by the display module is correct. There is something else happening inside the display module that is causing the information to be gabled. This is where I am reaching another boundary on my tool-set. The myAVR and the AVRISP MkII do not support any debugger. This is a set of tools I have yet to expose myself to and learn. Thinking around the problem, I am sure that this is some type of synchronisation issue. The actual cause, I can’t be sure. One thought would be some interference with the interrupt service routines. Since the data presented to and received by the display module is correct, I am willing to exclude the actual controller from the equation, for the moment. The problem has lead to some optimisations on the display module. One of the first improvements was seen when removing the NOSAVE from the On spi Spi_isr statement. This reduced the Spi_isr down to the following

Spi_isr:
   Set Rbit
   Mosi = SPDR
Return

The same routine as described in a previous blog entry also contained logic to arbitrate whether the incoming data was a register address or a value for the register. in fact, this was always a concern. How to reliably determine the name from the value. The very first entry after start-up is going to be an address. After that, if something happens to the timing, it is not so clear cut.

After trying several ways to analyse the problem and make changes that were not always successful, I have determined the most effective way would be to flag the address. This means the API to the display module has now changed such that the first byte send to it, as address, must have the most significant bit set. This is a major improvement on reliability, but still no solution!

Do
   If Rbit = 1 Then
      Reset Rbit
      Spdr = Mosi

      If Mosi.7 = 1 Then
         Idx = Mosi And &B00001111
      Else
         Register(idx) = Mosi ' Assign the data to the register
         Select Case Idx      ' Based on the register/value, set the affected variables
            Case Hours_10:
               Digits(1) = Register(idx)
       ...

There are still some issues to solve with this. For the above change, the controller board was not considered. However, the problem now comes back to the controller board to understand what the interaction is between the RTC, the controller board and the display board really is. Only once in a while at the transition of a minute, the display will be corrupted for a second or two. This does not happen often. Perhaps three to four times in an hour. But it shows there is still some form of timing race condition happening.

Where to from here?

I am happy that the communication is happening but I can’t call the job complete as there are some odd issues still persisting. I could work around them by sending only one byte rather than two so that the high end nibble is the address and the low end nibble is the value to be set. This would work for most of my values, but no all. Besides – that would be a work around and not a solution. I need to find a way to better understand what is happening with the data between the RTC, main board and display module such that the display modules register address and values are being skewed. At least that is my current understanding of the issue.

 

 

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.

LA6_Image_0020

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
Spiinit

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.

Spi_isr:
   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
      Else
         Register(idx) = SPDR
         Reset Spi_idx
      End If
   POP r24
   !OUT SREG, r24 ; restore sreg
   POP r24       ; and the used register
Return

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
            Else
               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.

Renderdisplay_isr: 
   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
   Else
      Value = Digits(digit)
   End If
   PORTD = Value
Return

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.

Powering up the μController Display Module

In the last post, I set up the infrastructure on my newly configured PC to make sure that it can at least communicate and program a chip. I needed to do this before jumping in and starting to program the μControll based display module to minimise the number of issues to be encountered.

Initial Check and Power up.

Initial testI am happy with the approach and it certainly did help. Though it did not minimise the number of issues encountered, it made them more manageable to solve.

Before powering up I went back over the schematic and lay out to be sure that there were any issues. I did have a doubt about the way in which I was handling the programmer pin header. As it turned out, my concern was correct but I could work around it. I just had to break out the pin header to another and connect up the reset pin correctly. The status lights of the AVRISP were a bit of a guide that there were still problems. As I worked through them, I finally got the all green lights! Now it was time to try my first on-board program. The image shows my basic test harness. To bread-board the controller board with a single seven segment display.

This was the first time I would be programming a micro controller on-board. This also mean verifying the tool chain as well. I had already mentioned about setting up a controller on the bread board and proving the setup with that. This time there is no easy way to swap a jumper lead if something is wrong. What I did learn was that the BASCOM IDE is not very helpful for connection issues. I did find the AVR Studio was much more informative. Only from the point of view that using their programmer, you can read the signature of the device. When that returns OK, then you know that the basic infrastructure is ready.

Integration Test

Integration TestNow that the board can be connected to and programmed, the next step was to connect the controller to the seven segment display board. Here I discovered another issue. Since this is all on the bread board, then it was easy to work around – Going back on the schematic I could see that I really did miss translating the pin-header changes I made to the controller board to the display board. A silly oversight but something to keep in mind for next time.


$regfile = "m88pdef.dat"
$crystal = 1000000
$hwstack = 40
$swstack = 16
$framesize = 32

' $baud = 4800


Config Portc = Output                       ' Digits
Config Portd = Output                       ' Segments

Dim Digit As Byte
Dim Count As Byte

Do
   For Count = 0 To 9
      Portd = Count                             
      For Digit = 0 To 5
         Portc = Digit
         Waitms 500
      Next                                  ' digit
   Next                                     ' count
Loop
End

Next Steps

I don’t need to go back and re-work the boards just yet. I can move on in their current state. The next step is to start the build the real firmware that will implement the expected API for the display module. Though now that I am this far, there is an interesting diversion to make with the project. Since this module has a SPI interface, it is possible to set this up as the main controller after all. That is to interface the Real Time Clock directly to this module so it displays the real time. This does not achieve the full objectives of the project though since it will not be able to implement any of the switches that are intended in the original design. But this could be an interesting way to test the digit display code.

Testing the μController Based Display – The Environment

This post is about bringing up the μController based display module for the Clock. In a previous post, I outlined the approach that I would be taking. Since this will be my first attempt at in-board programming on a “real” project, the idea is to take a systematic approach to try to quickly resolve any issues that might arise.

Setting up the Environment

muAVR mkII
muAVR mkII

To date, I have been working in the safe, insular world of myAVR and BASCOM on a newly configured PC. I have been lucky in that everything has “simply worked”. In order to start programming directly onto a project’s board, I need to start using the AVRISP mkII. Like I outlined in the previous post, the idea is to bread-board a controller with a LED. Using a simple “Blinker” program, I can ensure that the communication from PC to ISP to μController is correct without having to involve any issues that might be present on the display module board.

IMG_0767
AVRISP mkII
IMG_1113
Rough and ready test harness

As it turned out, this exercise was worth while. In order to communicate with the AVRISP mkII, I needed the appropriate drivers. This are installed with when installing the AVR Studio. This is a C/C++ IDE. My objective, at this stage is to stick with BASCOM. I will move to C at some later stage. In order to use the BASCOM-AVR IDE, I had to install the USB_LIB and set up a device filter, as per the BASCOM help document.

With the drivers installed correctly, I was able to see the AVRISP mkII display the correct status on its LEDs and I could then start programming the minimal “bread-boarded” μController.


$Regfile="m8def.dat"
$Crystal=4000000
$hwstack=40
$swstack=40
$framesize=60

' $baud = 4800
Config Portb = Output

Portb.0 = 1

Do
   Set Portb.0
   Waitms 1000
   Reset Portb.0
Loop
End

Next Step

The next steps will be to bring the board up slowly. That is to apply power and check that power is where it is intended to be. Once that checks out, the same test program used to verify the environment will be used to ensure that the board can actually be programmed. The test program will be the same “blink” example.

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.

Assembly

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.

150715-Stensils

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.

150715-HotAirPensil

Testing

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.

Assembly

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.

Display1-assembly-and-testing

Coding

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
 ...
Loop 
End

...

Pcint2_isr:
 If Second_interrupt = 0 Then New_second = 1
 If Dcf_interrupt = 0 Then Time_dcf = 0
Return

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
Spiinit

...
' 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 =================================================================

Init_display:

' 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

Return

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.

Conclusion

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.