In this post I describe the trials an tribulations of bringing up my Timed LED Lighting Controller and how I went about implementing my first I²C Slave Driver for the ATTiny20.
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.
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
Once 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.
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. 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.
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!
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 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
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.
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.
I 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.
Now 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
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.
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
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.
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
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.