Binary Clock using pic16f88 help

Discussion in 'Programmer's Corner' started by Carl03, Oct 27, 2012.

  1. Carl03

    Thread Starter New Member

    Oct 27, 2012
    Hi, I am new to the forum and I am in desperate need of help.
    I built the hardware to my binary clock I am making, my problem the code. I am new to the programming world and i am using MPLab to write the code, asm code. I would like to know if anyone can please help me in writing this code?
    I am using a 3x6 LED matrix display
    one row for hours, minutes and seconds with 6 LEDs in each row
    the hours is connected to RA0
    the minutes to RA1
    the seconds to RA2
    starting from right to left each LED in the row is connected to RB0(far right) up until RB5(far left)
    i also have to buttons for setting the time connected to RA3(hours set) and RA4(minutes set) when pressed should clear seconds
    Programming with PICKit3

    Any help would be welcomed.
    Thanx in advance
    Last edited: Oct 27, 2012
  2. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    Doesn't anyone use capital letters anymore? Capital letters at the start of sentences makes it easier for others to help you.

    Also what makes things easier is if you post your schematic, code and something other than "I have a problem with the code".

    What is the problem exactly and what have you done to debug it?

    Others might argue but I think with advanced languages such as C and BASIC (at least advanced to the mcu world) it is better to learn using mcus with one of those first. Learning mcus and reading data sheets is already a huge learning curve. It is my opinion (and again others will argue) that you learn the basics in a higher level language you can then come back to assembler once you have mcus tackled.
  3. JohnInTX


    Jun 26, 2012
    .. on to the programming then. As any programmer does, you need to break down the overall project into smaller, easier to understand (and implement) blocks. If you try to do it all at once, its a daunting task particularly if you are not experienced.

    For your clock, I would begin by breaking it down into something like this:

    0) PIC hardware configuration i.e. init the IO ports, configure the oscillator with CONFIG directives, etc.
    1) Timing i.e. generate a tik every second that is counted by--
    2) Internal timekeeping i.e. count up to 60, clear the seconds, increment minutes etc. then--
    3) Display the current count in the desired fashion
    4) i.e. Modify the current count using the buttons

    You may want to break it down further but you get the idea. Once you do, rough out a program flow for each part. Next code it and test in MPLAB. Note that you don't have to have it all at once. For example, 0 is simple enough.

    Make a subroutine called initIO that does all of the PORT and TRIS settings. Step through it using MPLAB and make sure all of the IO is ready to use before proceeding (LEDS off etc).

    Next, I would uses TMR2/PR2 to generate a periodic interrupt. It doesn't have to be a full second as you can count the interrupts like you count time. You can test this as well on MPLAB. When you are done, use the seconds output to flash one LED as a test..

    Continue with each of the other blocks, stitching them together when they work. Don't hesitate to write some disposable test code to exercise them both on MPLAB/MPSIM and on your target board. That's why you write things in small functional blocks rather than long drawn out code.

    Pretty much everything you need to write each of the blocks as I have described them have been discussed in depth on this forum, in the tutorial e-books on this forum and in various Microchip literature (tutorials, coding frameworks etc). Take small steps and you'll be on your way before you know it.

    A performer that ate an entire piano was asked how he did it. "One bite at a time" was the answer. That's how you do programming as well.

    Post your progress and questions here and you'll likely get all of the help you need.

    Have fun.

    spinnaker makes some excellent points here. A higher level language will relieve the programmer of much of the infernal details of managing the PIC 16Fs 'interesting' architecture and get things moving faster. I would add though that some familiarity with the underlying hardware is necessary regardless of the language used (in order to configure the PIC, program the peripherals, configuring the I/O etc) and being proficient in assembler makes that easier (and easy for ME to say because I'm proficient in assembler). One thing that a higher level language will not compensate for is bad programming practices nor will it relieve the programmer of the necessity of breaking the program down into manageable pieces and to have a plan to implement/test them. And, its sadly true that compilers and libraries can introduce their own bugs into the equation. When using C, I (and some other experienced guys here as well) write many of our own library functions, particularly for peripherals.

    The OP mentioned assembler so.. I like that, of course. But, yeah. Consider 'C' as well.
    Last edited: Oct 27, 2012
    Carl03 likes this.
  4. Carl03

    Thread Starter New Member

    Oct 27, 2012
    Thanx for the responses guys, I'm a student and have chosen this as my project. I am still learning C and assembler. For the project we have to code in assembler and all we have done in assembler was very basic like interrupt service routines, button debouncing, writing delay loops using counters. And I am learning now that I'll have to be using the TMR2 which we have not encountered as of yet but I came seeking help because I am enthusiastic about completing this project and seeing my work in action. As of yet I haven't done coding:(, well not much anyway.

    Code ( (Unknown Language)):
    2. LIST    p=16F88            
    3. #INCLUDE <P16F88.INC>
    8.     CBLOCK 0x20
    10.     sec
    11.     min
    12.     hour
    13.     button_hr
    14.     button_min
    15.     debounce
    16.     counter
    17.     ENDC
    19. #define HOUR    PORTA,0
    20. #define MIN     PORTA,1
    21. #define SEC     PORTA,2
    22. #define BUTTON1 PORTA,3
    23. #define BUTTON2 PORTA,4
    25. #define LED0 PORTB,0
    26. #define LED1 PORTB,1
    27. #define LED2 PORTB,2
    28. #define LED3 PORTB,3
    29. #define LED4 PORTB,4
    30. #define LED5 PORTB,5
    33. ORG 0x0000
    34. nop
    35. goto start
    36. ORG 0x0004
    37. isr
    39. start
    40.     clrf    TRISA
    41.     clrf    PORTB
    42.     bsf STATUS, RP0
    43.     movlw b'00011000'       ;RA0-2 outputs, RA3-4 inputs
    44.     movwf TRISA
    45.     movlw b'00000000'
    46.     movwf TRISB
    47.     bcf STATUS, RP0
    48.     bcf INTCON, TMR0IF
    49.     bsf INTCON, TMR0IE 
    50.     bsf INTCON, GIE    
    That's all I have at the moment. I have also attached the schematic.

    Thanx again for taking the time out to reply.
  5. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    You still haven't stated your problem or what you have done to troubleshoot the problem.

    As JohnInTX suggested, break the problem down into small pieces. If you have to write a small program for the piece that is giving you problems. Get that working then move on.

    You need to do something to help yourself. We are happy to offer advice, answer questions and get you over the rough spots.

    Folks around here are happy to help but we are not going to do your work for you especially when it comes to a school project. The way to learn is to make mistake then fix them now. I'd rather you do that then do it on the navigation system for an airplane that I might be flying on some day. ;)
  6. JohnInTX


    Jun 26, 2012
    Some housekeeping first:
    Tab your ORG stuff off of column 1. That's reserved for labels.
    Terminate your labels with a colon :
    clrf PCLATH before you goto your start routine.
    Do not set TMR0IE yet, you haven't configured the timer.
    Do NOT set GIE yet. You don't have an interrupt service routine.
    (some of these don't matter yet, but eventually they might).

    In your start routine, move the IO initialization to a subroutine like:

    Code ( (Unknown Language)):
    1. initIO:
    2.   do stuff
    3. return
    then call it from your start routine.

    In initIO be sure to configure all of it:

    You'll need to clear ANSEL to make the IO digital (lots miss this).
    Also, your TRIS registers are in bank 1. Your clrf TRISA is in the wrong bank.
    Use banksel to select the banks. Avoid STATUS,RPx unless you track the current bank or manually maintain both RP1 and RP0.
    Select bank 0 and set up all of your outputs then select bank 1, do all of the TRIS/ANSEL stuff.
    Consider not relying on power-on defaults but initializing ALL of the IO/ADC/Comparator/Timers et al explicitly. You may have occasion to re-run inits from an unknown (not power on) condition. Plus, visiting all of the chip's registers is a good introduction to them.

    Once you have that, call it from start followed by a couple of nops. Set a PK3 breakpoint there and run the program. Use a watch window to look at the IO setup (TRISx, ANSEL etc.) to be sure the values match what you expect.

    After that, try a few outputs to the LEDs followed by breakpoints (so that you don't have to write more code) Write 1 to RA0 and 0 to RB5 followed by some nops, break and verify that the LED lights. Buzz out the LED IO as necessary and confirm that you can address each LED.

    Same with the switches. Write a loop at the end of the LED stuff that reads the buttons to W. Break after that and examine W while pressing the buttons to make sure that the bits show up.

    Wow! you just buzzed out all of your hardware.

    Since you are stepping and stopping, I would be tempted to write the code that outputs hours,mins,secs on the LEDs next. Since you are scanning it, start by writing the scan routine as a subroutine and call it from a loop in start using breakpoints after each one.

    When you have that working, configure TMR0 to interrupt the PIC at some interval (10ms or so) and call the scanner each IRQ. The main loop now is a dummy loop that initsIO, does some nops, clears WDT then loops to somewhere after initIO. The IRQ does the rest. You can manually poke in values for hours,min,sec and watch the LEDs.

    After that, add TMR2 secs counter to run the h,m,s registers.

    Small steps, each building on tested stuff. Not all you need to know but you get the idea. Since you are learning as you go along, each thing you do should be bite sized and easily testable. Once happy with it, build on it.

    Of course, the whole thing is guided by your software design / flow chart. If you haven't yet, use this as guide and do it.

    +1 That's why I get grumpy about things like full initializations, modularity and all the rest!
    Last edited: Oct 27, 2012
  7. Carl03

    Thread Starter New Member

    Oct 27, 2012
    I am not sure how to start the coding. Like you said initialize the IO ports, I'm a total noob to all of this. Needing help to start my code, I get what I'm suppose to do but atm it's not clear to me how I should start. The way you explaining to me is not how I've been taught and I know you trying to lead me in the right direction but I've never heard of banksel and ansel, I'm learning slowly but surely how to use it, just not fast enough.
  8. t06afre

    AAC Fanatic!

    May 11, 2009
    You would not have gotten this assignment without having some preparation both by lessons and lab work. And I think this is some final course assignment. Where you shall use what you have learned before. I think you are confused because this is your first real world task. You do not have some text to lean on. A very common way to make a clock. Is to use a 32.768kHz crystal on Timer1.
    Then let Timer1 generate an interrupt every second. Start simple by using this to toggle a LED every second
  9. spinnaker

    AAC Fanatic!

    Oct 29, 2009

    You are confused? How do you think we feel. You already have a lot of code. What does it do? Who wrote it?

    Look at what you have and then try to define what about it does not work.
  10. JohnInTX


    Jun 26, 2012
    The good news here is that the PIC does not give a rat's patoot what you've been taught or if BANKSEL / ANSEL is news to you. That simple fact sets you free to begin. But begin you must.

    So write initIO as a subroutine. Read the 16F88 databook - not the whole thing. Read Ch 5 - I/O ports. In there is everything you need to know about setting up the IO, including dealing with ANSEL, highlighted in a grey box.

    Ch 12 describes ANSEL and its worth a look but just clear it for now to set the pins to digital IO.

    BANKSEL is used all over the databook but its not explained. That's because it is an assembler directive, not a PIC instruction. Like ORG, CBLOCK and END, it tells the assembler to do something for you.

    BANKSEL name

    asks the assembler to generate the code (in this case flipping RPx bits in STATUS) to select the RAM bank that 'name' is in. BANKSEL and other directives are documented in MPASM help. You don't have to use it but the goal is to make your code easier to read and understand. bcf STATUS,RPx tells you that you changed RAM banks. BANKSEL ANSEL not only tells you that you are thinking about ANSEL but also guarantees that RPx will be set up correctly.

    Here's something to get you started. Its only the framework. You have code initIO.
    (I didn't try to assemble this so you might see some syntax errors.)

    Code ( (Unknown Language)):
    1. ;************************ CARL03.asm  **********************************
    3. LIST    p=16F88                
    4. #INCLUDE <P16F88.INC>
    9.     CBLOCK 0x20
    11.     sec
    12.     min
    13.     hour
    14.     button_hr
    15.     button_min
    16.     debounce
    17.     counter
    18.     ENDC
    20. #define HOUR    PORTA,0
    21. #define MIN     PORTA,1
    22. #define SEC     PORTA,2
    23. #define BUTTON1 PORTA,3
    24. #define BUTTON2 PORTA,4
    26. #define LED0 PORTB,0
    27. #define LED1 PORTB,1
    28. #define LED2 PORTB,2
    29. #define LED3 PORTB,3
    30. #define LED4 PORTB,4
    31. #define LED5 PORTB,5
    34.     ORG 0x0000
    35.     nop
    36.     clrf    PCLATH        
    37.     goto     start
    39.     ;**************** INTERRUPT SERVICE *********************
    41.     ORG 0x0004
    42.     ; IRQ service TBD
    43.     nop
    44.     nop
    45.     ; You never should be here since you don't have any IRQs set up yet
    46.     ; but if you ARE, this will stop it gracefully.
    47.     bcf    INTCON,GIE    
    48.     return
    50.     ;**************** MAIN PROGRAM **************************
    51. start:
    52.     call    initIO
    54. sysLoop:
    55.     BANKSEL    sec        ; every routine should set up its own banks, so we do it
    56.     nop
    57.     nop            ; set a breakpoint here and examine the results of initIO
    58.     nop
    60.     bsf    HOUR        ; turn on one LED
    61.     bcf    LED0
    63.     nop
    64.     nop            ; set a breakpoint here
    65.     nop
    66.     goto    sysLoop
    68.     ;******************* INIT IO  ****************************
    69.     ; Inits all of the PIC IO
    70.     ; Call any bank
    71.     ; Rets undefined bank    <- your indication that you must maintain banks
    72.     ;    yourself.
    73.     ; Fill in the blanks    
    75. initIO:
    76.     BANKSEL    PORTA
    77.     ;init port A and B output bits (same bank for both so one BANKSEL will do
    79.     BANKSEL TRISA
    80.     ; init port A and B TRIS
    82.     clrf    ANSEL        ; ANSEL is in bank 1 too.
    84.     ; note that initIO changes the RAM bank selection, a potentially nasty side-effect
    85.     ; Personally, I like each routine to set up its banks as required so leaving banks
    86.     ; in any state is OK since the next routine will set up as required.
    87.     ;
    88.     ; Others like to enforce a default bank that each routine must return. A typical default
    89.     ; bank would be 0 to bring the IO ports back into scope.
    90.     ; There are advantages/disadvantages to either approach.
    92.     ; For a beginner, I would recommend the latter approach i.e. always return bank 0
    93.     ; The important thing is to decide on a method and apply it consistently.
    95.     return
    97.     END        ; required for MPASM
    If you are fuzzy on debugging with MPLAB, stop down and take a look at this:
    Step by step
    Last edited: Oct 28, 2012
  11. Carl03

    Thread Starter New Member

    Oct 27, 2012
    So this is what I've done so far. The 'state machine' part in my code is the multiplexing part of my code and I am able to displaying numbers as I would like but can only do so manually through my programming.

    Code ( (Unknown Language)):
    1. ;************************ CARL03.asm  **********************************
    3.     LIST    p=16F88                
    4. #INCLUDE <P16F88.INC>
    9.     CBLOCK 0x20
    11.     sec
    12.     min
    13.     hour
    14.     button_hr
    15.     button_min
    16.     debounce
    17.     counter
    18.     current_state
    19.     ENDC
    21. #define HOUR_EN PORTA,0
    22. #define MIN_EN  PORTA,1
    23. #define SEC_EN  PORTA,2
    24. #define BUTTON1 PORTA,3
    25. #define BUTTON2 PORTA,4
    27. #define LED0 PORTB,0
    28. #define LED1 PORTB,1
    29. #define LED2 PORTB,2
    30. #define LED3 PORTB,3
    31. #define LED4 PORTB,4
    32. #define LED5 PORTB,5
    34. #define incw    addlw d'1'
    37.     ORG 0x0000
    38.     nop
    39.     clrf    PCLATH        
    40.     goto     start
    42.     ;**************** INTERRUPT SERVICE *********************
    44.     ORG 0x0004
    45.     ; IRQ service TBD
    46.     nop
    47.     nop
    48.     ; You never should be here since you don't have any IRQs set up yet
    49.     ; but if you ARE, this will stop it gracefully.
    50.     bcf    INTCON,GIE    
    51.     return
    53.     ;**************** MAIN PROGRAM **************************
    54. start:
    55.     call    initIO
    57. sysLoop:
    58.     call    state_machine
    59.     goto    sysLoop
    61. state_machine:
    62.     movlw   .0
    63.     xorwf   current_state, w
    64.     btfsc   STATUS, Z
    65.     goto    state_machine_hours
    66.     movlw   .1
    67.     xorwf   current_state, w
    68.     btfsc   STATUS, Z
    69.     goto    state_machine_min
    70.     goto    state_machine_sec
    72. state_machine_hours:
    73.     bcf     SEC_EN
    74.     comf    hour, w
    75.     movwf   PORTB
    76.     bsf     HOUR_EN
    77.     incf    current_state, f
    78.     goto    state_machine_end
    79. state_machine_min:
    80.     bcf     HOUR_EN
    81.     comf    min, w
    82.     movwf   PORTB
    83.     bsf     MIN_EN
    84.     incf    current_state, f
    85.     goto    state_machine_end
    86. state_machine_sec:
    87.     bcf     MIN_EN
    88.     comf    sec, w
    89.     movwf   PORTB
    90.     bsf     SEC_EN
    91.     clrf    current_state
    92. state_machine_end:
    93.     return
    94.     ;******************* INIT IO  ****************************
    95.     ; Inits all of the PIC IO
    96.     ; Call any bank
    97.     ; Rets undefined bank    <- your indication that you must maintain banks
    98.     ;    yourself.
    99.     ; Fill in the blanks    
    101. initIO:
    102.     BANKSEL PORTA
    103.     clrf    PORTA
    104.     clrf    PORTB
    105.     movlw   b'00011000'
    106.     movwf   PORTA
    107.     movlw   b'11111111'
    108.     movwf   PORTB
    109.     ;init port A and B output bits (same bank for both so one BANKSEL will do
    111.     BANKSEL TRISA
    112.     ; init port A and B TRIS
    113.     clrf    TRISA
    114.     clrf    TRISB
    116.     clrf    ANSEL        ; ANSEL is in bank 1 too.
    117.     movlw   b'00000000'
    118.     movwf   OSCCON
    119.     banksel sec
    120.     movlw   .00
    121.     movwf   sec
    122.     movlw   .00
    123.     movwf   min
    124.     movlw   .12
    125.     movwf   hour
    127.     ; note that initIO changes the RAM bank selection, a potentially nasty side-effect
    128.     ; Personally, I like each routine to set up its banks as required so leaving banks
    129.     ; in any state is OK since the next routine will set up as required.
    130.     ;
    131.     ; Others like to enforce a default bank that each routine must return. A typical default
    132.     ; bank would be 0 to bring the IO ports back into scope.
    133.     ; There are advantages/disadvantages to either approach.
    135.     ; For a beginner, I would recommend the latter approach i.e. always return bank 0
    136.     ; The important thing is to decide on a method and apply it consistently.
    138.     return
    140.     END        ; required for MPASM
    I would like to know what I have to do next, how to go about the timing and so on. thanks
  12. JDT

    Well-Known Member

    Feb 12, 2009
    If you are a beginner with PICs start with small steps. Don't copy other peoples code - you won't learn that way.

    From the Microchip website you can download the assembly code template for this device. (Nice to see you using assembler BTW).

    Also download the datasheet and if possible, print out the whole thing.

    Using this template the first thing you need to do for a digital clock is to program an interrupt that occurs every second. Using one of the counter timers. Most of your code for the clock will be in the interrupt.

    Non-interrupt code will be a one-time initialization and then stick in an endless loop.

    Then make it do something simple like toggle a LED every second in your interrupt code. Just to prove that your interrupt is working. Program the chip. Test it.

    Then add a bit more code. Repeat.

    If you try to do too much at once and it doesn't work it can be very difficult to debug.

    Good luck!
  13. t06afre

    AAC Fanatic!

    May 11, 2009
    Why not take a look at this
  14. Carl03

    Thread Starter New Member

    Oct 27, 2012
    In my code I've put my state machine section into a timer 0 interrupt. So now I have started a timer 2 interrupt for the actual time keeping but my problem is that I have all the lights flashing row by row after putting in my own delay, where it should actually increment the sec by a second

    Here is the full interrupt:
    Code ( (Unknown Language)):
    1.    ;**************** INTERRUPT SERVICE *********************
    2.     ORG 0x0004
    3. isr:  
    4.         movwf   W_TEMP            ; save off current W register contents
    5.         movf    STATUS,W          ; move status register into W register
    6.         movwf   STATUS_TEMP       ; save off contents of STATUS register
    7.         movf    PCLATH,W          ; move pclath register into W register
    8.         movwf   PCLATH_TEMP       ; save off contents of PCLATH register
    10. ; Test to see if the interrupt was generated by TMR0
    12.         btfsc   INTCON,TMR0IF    ; TMR0 interrupt?
    13.         goto    tmr0int          ; Yes
    14.         btfsc   PIR1, TMR2IF
    15.         goto    tmr2int
    16.         goto    intout
    19. tmr0int:
    20.         bcf     INTCON, TMR0IF   ; clear interrupt flag (HAVE TO DO THIS)
    22. state_machine:
    23.         movlw   .0
    24.         xorwf   current_state, w
    25.         btfsc   STATUS, Z
    26.         goto    state_machine_hours
    27.         movlw   .1
    28.         xorwf   current_state, w
    29.         btfsc   STATUS, Z
    30.         goto    state_machine_min
    31.         goto    state_machine_sec
    33. state_machine_hours:
    34.         bcf     SEC_EN
    35.         comf    hour, w
    36.         movwf   PORTB
    37.         bsf     HOUR_EN
    38.         incf    current_state, f
    39.         goto    state_machine_end
    40. state_machine_min:
    41.         bcf     HOUR_EN
    42.         comf    min, w
    43.         movwf   PORTB
    44.         bsf     MIN_EN
    45.         incf    current_state, f
    46.         goto    state_machine_end
    47. state_machine_sec:
    48.         bcf     MIN_EN
    49.         comf    sec, w
    50.         movwf   PORTB
    51.         bsf     SEC_EN
    52.         clrf    current_state
    53. state_machine_end:
    54.         goto    intout
    56. tmr2int:
    57.         bcf     PIR1, TMR2IF     ; clear interrupt flag (HAVE TO DO THIS)
    58.         banksel sec
    59.         movf    sec,W       ; Bump the seconds count by one
    60.         addlw   .7
    61.         btfss   STATUS,DC
    62.         addlw   -.6
    63.         movwf   sec
    64.         xorlw   h'60'           ; Have we reached 60?
    66. intout:
    67.         movf    PCLATH_TEMP,W     ; retrieve copy of PCLATH register
    68.         movwf   PCLATH            ; restore pre-isr PCLATH register contents
    69.         movf    STATUS_TEMP,W     ; retrieve copy of STATUS register
    70.         movwf   STATUS            ; restore pre-isr STATUS register contents
    71.         swapf   W_TEMP,F
    72.         swapf   W_TEMP,W          ; restore pre-isr W register contents
    73.         retfie                    ; return from interrupt
    75. ;Delay - this is the delay I made
    76. delay:
    77.     movlw .255
    78.     movwf counter
    79.     del_loop:
    80.     nop
    81.     nop
    82.     decfsz counter, f
    83.     goto del_loop
    84.     return
    86. long_delay:
    87.     movlw .255
    88.     movwf counter_1
    89.     long_del:60
    90.     call delay
    91.     decfsz counter_1, f
    92.     goto long_del
    93.     return         
    Any ideas to take a step forward and then as well how to bump the minutes by 1 after the seconds has reached 60?
  15. Carl03

    Thread Starter New Member

    Oct 27, 2012
    Ok. So I've my time keeping going really well with increments of 1 by the seconds every second...working perfectly. Now, on to the button programming. Can someone help me with it please?
  16. atferrari

    AAC Fanatic!

    Jan 6, 2004
    I recall seeing that in old code (Microchip).

    What for?

    Honestly I do not see any chance, at least in my code, that I could confuse labels with anything else.