Button Interrupt Failing [PIC][XC8] - Page 1
Forum section: electronics / general

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 27, 2013, 12:35:05 am
content: Hello Everyone,This is a total stumper that I have been working on for hours. First off I bit the bullet and moved my whole alarm clock from assembler to C. YEY for me so much more productive actually.I have recently really hit a solidmoment.Obviously to have an alarm clock you need buttons. Well I have buttons and so far every single button works correctly minus this damn alarm enable button. Essentially you press the button and it enables the capability for the alarm to go off at it's designated time. When you start the program up and press the enable button it works sometimes not all the time. If you use any other button before hitting this one it does not work. If you get it to work right off the bat and hit any other buttons it stops working. If the alarm goes off and you press it everything works but then you can't re enable the alarm after changing the time. It is so weird I have ran through this code for the last 3 hours and am not finding the issue. Another note is that if I change the alarm time while this is enabled the alarm never goes off. This is so weird because until I put in this button everything was working great.The buttons do not bounce and are de bounced using caps.Really maybe someone here can see the obvious issue because I am getting no where at this point. The last thing I need to do is get this feature + snooze in and the clock is done ugh!!!!!OH and sorry for the really messy code I tried to keep it neat and tidy!OH one more thing sometimes the only way to get the alarm to work is to re program the chip and even sometimes that does not work!!!!! WTF!!!For some reason I think something is hanging somewhere but at the same time does not effect the other interrupts so I am not sure what is going on. The hardware debugger is not helping me at all here.Code:#include <xc.h>// PIC16F1782 Configuration Fuse Settings// CONFIG1#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)#pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled)#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)// CONFIG2#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)#pragma config VCAPEN = OFF // Voltage Regulator Capacitor Enable bit (Vcap functionality is disabled on RA6.)#pragma config PLLEN = OFF // PLL Enable (4x PLL disabled)#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)#pragma config LPBOR = OFF // Low Power Brown-Out Reset Enable Bit (Low power brown-out is disabled)#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)// Global Variables// display digit lookup table// each segment pin needs to be turned on to make a digit// the circuit has the A-G pins hooked up to a I/O port where A-G = 0-6 respectively// the format for the bits is 0bGFEDCBAvolatile unsigned char digit_tbl = {0b00111111, // digit 00b00000110, // digit 10b01011011, // digit 20b01001111, // digit 30b01100110, // digit 40b01101101, // digit 50b01111101, // digit 60b00000111, // digit 70b01111111, // digit 80b01101111 // digit 9};volatile unsigned char interrupt_cnt = 0; // number of times interrupt is called (250 = 1 second)volatile unsigned char seconds = 0;// when clock is started it defaults to 12:00volatile unsigned char hours = 12;volatile unsigned char minutes = 0;volatile unsigned char alarm_hours = 12;volatile unsigned char alarm_minutes = 1;volatile bit alarm_active = 0;volatile bit alarm_set_enabled = 0;volatile bit alarm_enabled = 0;// extracts the first digit from an integer ie. the 1 in the number 12unsigned char extract_dig1(volatile const unsigned char * value){return *value / 10;}// extracts the second digit from an integer ie. the 2 in the number 12unsigned char extract_dig2(volatile const unsigned char * value){return *value % 10;}// updates the display digitsvoid update_display(volatile const unsigned char * h_val, volatile const unsigned char * m_val){if (LATB0) {// Display 0 was updated last update digit 1LATB0 = 0; // turn off display 0LATB1 = 1; // turn on display 1LATC = digit_tbl[extract_dig2(h_val)]; // load the segments with the proper valuereturn;} else if (LATB1) {// Display 1 was updated last update display 2LATB1 = 0; // turn off display 1LATB2 = 1; // turn on display 2LATC = digit_tbl[extract_dig1(m_val)]; // load the segments with the proper valuereturn;} else if (LATB2) {// Display 2 was updated last update display 3LATB2 = 0; // turn off display 2LATB3 = 1; // turn on display 3LATC = digit_tbl[extract_dig2(m_val)]; // load the segments with the value 0return;} else {if (*h_val > 9) {// Either display 3 was updated last or no display is enabled (first interrupt cycle only)LATB3 = 0; // turn off display 3// on first run this is a wasted instruction but saves program spaceLATB0 = 1; // turn on display 0LATC = digit_tbl[extract_dig1(h_val)]; // load the segments with the proper valuereturn;} else {// we do not need to use display 0 so update display 1 insteadLATB3 = 0; // turn off display 3LATB1 = 1; // turn on display 1LATC = digit_tbl[extract_dig2(h_val)]; // load the segments with the proper valuereturn;}}}void interrupt interrupt_service(void){// Check for Timer0 overflow interruptif (TMR0IE && TMR0IF) {TMR0IF = 0; // clear interrupt flaginterrupt_cnt++; // update the interrupt called count// Update our time if necessaryif (interrupt_cnt == 250) {// One second has passedseconds++;if (seconds == 60) {seconds = 0;minutes++;if (minutes == 60) {minutes = 0;hours++;if (hours == 13) {hours = 1;}}}// Toggle alarm if triggeredif (alarm_active)LATA0 ^= 1;}// The overall display is a multiplexed 4 display 7 segment unit// displays corrispond to pins on PORTB in this circuit.// Display 0 is on LATB0 - Display 3 is on LATB3// In the circuit these displays are switched through NPN transistorsLATC = 0; // Clear all lit segments to prevent ghosting.// Update the appropriate display for this interrupt cycleif (alarm_set_enabled) {// we need to display the alarm so we can set the timeupdate_display(&alarm_hours, &alarm_minutes);return;} else {update_display(&hours, &minutes);return;}} else if (IOCIE && IOCIF) {if (IOCAF1) {IOCAF1 = 0; // clear interrupt flag// need to update to appropriate hoursif (alarm_set_enabled) {alarm_hours++;if (alarm_hours == 13)alarm_hours = 1;return;} else {hours++;if (hours == 13)hours = 1;return;}} else if (IOCAF2) {IOCAF2 = 0; // clear interrupt flag// need to update appropriate minutesif (alarm_set_enabled) {alarm_minutes++;if (alarm_minutes == 60)alarm_minutes = 0;return;} else {minutes++;if (minutes == 60)minutes = 0;return;}} else if (IOCAF3) {IOCAF3 = 0; // clear interrupt flagalarm_set_enabled ^= 1; // enable alarm set modereturn;} else if (IOCAF5) {IOCAF5 = 0; // clear interrupt flagalarm_enabled ^= 1; // toggle alarm_enabled statereturn;}}}void main(void){/** OSCCON Register* bit 0 = Software PLL Enable* bit 6:3 = Internal oscillator frequency* bit 2 = Unimplemented* bit 1:0 = system clock select*/OSCCON = 0b01101000; // 4 MHZ oscillator frequency clk selected via FOSC fuseANSELA = 0; // ensure PORTA analog inputs are disabledANSELB = 0; // ensure PORTB analog inputs are disabledTRISA = 0b00111110; // set RA0 as output RA1 - RA5 as inputTRISB = 0; // set PORTB data direction to outputTRISC = 0; // set PORTC data direction to outputPORTA = 0;LATB = 0;LATC = 0;TMR0 = 0; // clear the Timer0 counter and prescaler// Enable interrupt on change falling edge for PIN RA1 - RA5IOCAN = 0b00111110;/** INTCON* bit 7 = Global Interrupt Enable* bit 6 = Periferal Interrupt Enable* bit 5 = Timer0 Overflow Interrupt Enable* bit 4 = INT External Interrupt Enable* bit 3 = Interrupt-On-Change Interrupt Enable* bit 2 = Timer0 Overflow Interrupt Flag* bit 1 = INT External Interrupt Flag* bit 0 = Interrupt-On-Change Interrupt Flag*/INTCON = 0b10101000; // enable global interrupts, enable Timer0 interrupt and pin change interrupts/** OPTION_REG Register* bit 7 = Weak Pull-Up Enable* bit 6 = Interrupt Edge Select* bit 5 = Timer0 Clock Source Select* bit 4 = Timer0 Source Edge Select* bit 3 = Prescale Assignment* bit 2:0 = Prescale Rate*/OPTION_REG = 0b11010011; // bit 7:6 is not used by Timer0// set timer to use internal instruction clock (FOSC/4)// bit 4 is not used and prescale Timer0 by 1:16// this will give us a overflow interrupt of aprox. 4mswhile (1){if (alarm_enabled) {LATB4 = 1;if (hours == alarm_hours && minutes == alarm_minutes)alarm_active = 1;elsealarm_active = 0;}else {LATB4 = 0;alarm_active = 0;}}}

author: croberts
author_rank: Regular Contributor
timestamp: May 27, 2013, 01:57:32 am
content: I'm not sure how C handles it but in assembler it is essential to save context when entering the interrupt routine and then recovering it before returning to the main loop. I don't see this in your code but maybe C somehow does it for you.

author: David_AVD
author_rank: Super Contributor
timestamp: May 27, 2013, 02:02:23 am
content: That code doe seem to be doing a lot in the ISR. I tend to do the minimum amount in the ISR and use flags set there to perform tasks in the main loop.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 27, 2013, 02:13:43 am
content: Quote from: David_AVD on May 27, 2013, 02:02:23 amActually there is really not too much going on in the ISR at any one given time. Just basic multiplexed display updates, the time keeping for the clock and some flag updates. There is only one ISR vector on PIC16 chips so you can't break it out into separate vectors like you can on the AVR. Hence the reason for the ISR flag checks to see which set of interrupt code needs to get executed. Truthfully this is one of the largest downfalls of the PIC.As for state saving I am not sure I would come to think that the compiler generates the code for it but there really is not much state to save as there is only one accumulator so I do not think it is that. Everything was working fine until I had to add the IOCAF5 button check which lead to nesting the alarm code in the main loop under the alarm_enabled flag check.

author: David_AVD
author_rank: Super Contributor
timestamp: May 27, 2013, 02:23:47 am
content: Do you have a schematic ?

author: Paul Price
author_rank: Super Contributor
timestamp: May 27, 2013, 02:40:03 am
content: How do you know the button does not bounce? Have you hardware circuit to prevent bounce? If, not scope it.Try looking at the changes. It looks like you are toggling the alarm enable every second.All changes by me have PPC in the comment line somewhereI would change the volatile declarations to static for the few vars you have, esp. the alarm_activeExplain what your code's intention is here:if (interrupt_cnt == 250) {// One second haspassedinterrupt_cnt=0; //must reset the counter or else you get extra 5 counts and you will wake up late /b]seconds++;if (seconds == 60) {seconds = 0;minutes++;if (minutes == 60) {minutes = 0;hours++;if (hours == 13) {hours = 1;}}}// Toggle alarm if triggered//PPC What does "LATA0" mean, refer to, action of?? Please explain//PPC is "LATA0" a not a variable...it is not declared.//Must be some register or flag. Please explain.if (alarm_active)LATA0 ^= 1; //PPC

author: Rufus
author_rank: Super Contributor
timestamp: May 27, 2013, 02:51:44 am
content: You have PORTA bit 4 set as input with interrupt on change enabled and nothing in the interrupt handler for it.One change on that pin will have the processor permanently taking interrupts although it looks like it should fumble through and sort of work regardless.Not your main problem but:you should also be setting interrupt_cnt back to 1 when it reaches 250.you should move the alarm active check from the main loop to the interrupt handler.You have more things than you need declared volatile. If you move the alarm active check nothing needs to be volatile.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 27, 2013, 02:55:00 am
content: Thanks for the efforts I just found the issue. I was getting a false positive on an interrupt flag somewhere. Found this by lots of trial and error. I realized the actual alarm check code under certain conditions was never reached which meant I was stuck in the ISR until another interrupt aka a button press kicked it out if and only if it cleared out the false positive.Here is the modified code. I started by instead of clearing just one IOCAF bit I changed it to clear the whole register. Then I added an else to do the same incase of a false positive.Works perfectly now. While I was testing I did notice a switch bounce when pressing the buttons quickly so I might need to drop the value of the caps or the resistor so they charge faster.Ah thanks Rufus did not realize I forgot to reset the interrupt counter that would explain the small deviation I see on my scope every so often.Code:} else if (IOCIE && IOCIF) {if (IOCAF1) {IOCAF = 0; // clear interrupt flag// need to update to appropriate hoursif (alarm_set_enabled) {alarm_hours++;if (alarm_hours == 13)alarm_hours = 1;return;} else {hours++;if (hours == 13)hours = 1;return;}} else if (IOCAF2) {IOCAF = 0; // clear interrupt flag// need to update appropriate minutesif (alarm_set_enabled) {alarm_minutes++;if (alarm_minutes == 60)alarm_minutes = 0;return;} else {minutes++;if (minutes == 60)minutes = 0;return;}} else if (IOCAF3) {IOCAF = 0; // clear interrupt flagalarm_set_enabled ^= 1; // enable alarm set modereturn;} else if (IOCAF5) {IOCAF = 0; // clear interrupt flagalarm_enabled ^= 1; // toggle alarm_enabled statereturn;} else {IOCAF = 0;return;}

author: Rufus
author_rank: Super Contributor
timestamp: May 27, 2013, 03:12:27 am
content: You should take note of the rest of what I said. Your code has one of the most horrid kind of bugs. In this program it will only cause a glitch which will be quickly corrected in others it could be disastrous.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 27, 2013, 03:42:08 am
content: Quote from: Rufus on May 27, 2013, 03:12:27 amYes I know PORTA4 is set as input there is actually a button with a pull-up resistor on it right now. That is for the snooze button did not get around to implementing it yet as I wanted to fix the other darn bugs first. But it is being held high and set to interrupt on falling edge so in this case it should not be causing any bugs but yes I am implementing that next.I will also move the alarm check code to the ISR. One thing I must be misunderstanding is I thought variables used in the ISR had to be volatile or they would get optimized out.

author: poorchava
author_rank: Super Contributor
timestamp: May 27, 2013, 06:19:05 am
content: I would look at the disassembly and check if there is really an interrupt and context save / restore. Had some bad experience with XC8 in this regard.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 27, 2013, 09:31:18 am
content: Quote from: poorchava on May 27, 2013, 06:19:05 amIn all honesty it has been nothing but headaches with XC8. The code bloat is insane. I did a comparison of the free compile vs the Pro Trial and you are quite literally looking at close to a 25% difference with my particular firmware. What makes it worse is if you do look at the disassembly it is all intentional artificial code bloat. With random 3 - 4 goto's tossed everywhere which is 2 - 3 branches too many. Never saw such horror.As for the context save keep in mind the enhanced mid-range has a automatic context save built in the hardware for interrupts where it automatically saves WREG, STATUS, BSR, FSR, and PCLATH, as well as the PC address.

author: Rufus
author_rank: Super Contributor
timestamp: May 27, 2013, 11:54:09 am
content: Quote from: blewisjr on May 27, 2013, 03:42:08 amThat is what will fix the horrid bug. I'm trying to let you discover what the bug is.

author: poorchava
author_rank: Super Contributor
timestamp: May 27, 2013, 12:01:19 pm
content: Quote from: blewisjr on May 27, 2013, 09:31:18 amThe last remark is not exactly right - compiler is so dumb, that in some cases it will do 'emergency context save' which means it will put ALL the core registers and probably something else too on the stack with software, no hardware involved. This causes context switch to be over 100 cycles in some cases. Another case of Microchip trying to show you that you need to buy the paid version instead of using the free one, I guess. Best way I found to avoid it was to not call any function withing ISR and specify explicitly which registers should be saved on ISR entry (check our the XC8 manual for specifics). I'm sure there still my thread somewhere in depths of this forum.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 28, 2013, 02:15:20 am
content: Yes I much prefer AVR when it comes to C code. Even their assembler is a little bit better due to multiple GPR's. I like the actual PIC chips themselves much better just because of the shear feature set you get off of comparable devices. I guess you can't win everywhere. Originally the clock was being done on an AVR but I had to switch to my PIC chips I had laying around because on the ATtiny2313 I ran out of pins I could use. So I had to switch to a 28 Pin PIC as I don't have any AVR's over 20 Pins. There might be a way to make the 20 pin chip work but that will be left for a second project or maybe a alarm clock rework project. I am sure there is a better way to do this without using as many pins. Either way I am still excited that my first project is almost complete. Just need to do the schematic and the PCB/Project case. Feels really good to complete something like this.

author: poorchava
author_rank: Super Contributor
timestamp: May 28, 2013, 07:13:15 am
content: XC8 bullshit was the final factor which made me go for ARM-Cortex (together with poor performance and silicon bugs). Learning curve is kinda steep at the beginning, but when you have all the basic stuff figured out it only gets easier and easier.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 28, 2013, 10:19:02 am
content: Oh yes the silicon bugs the joy. It is amazing how those things get through the process. Some of them are so bad they even say don't do this or use this feature there is no work around. Seems kind of pointless. I understand they have lots of older rev chips out there but you think they would have extensive testing to avoid that kind of stuff before they go to mass production. Oh well.I do like your motto there. PIC has poor C support from the hobby point of view. AVR often gimps features on a lot of the chips making it hard to get a perfectly affordable one for the hobby project. The next best thing is ARM. The main thing that gimps ARM is the package formats. Would be nice to see some ARM DIPs.As for learning curve they all have one. Some with the chip and some with the development tools. I think Atmel has the dev tools down well at least on windows. PIC has so many chip variations it makes it easy to pick one for the project so I would have to say PIC has the chip side down in the 8 bit world. ARM is just a powerhouse on every front but are over kill for tons of projects. But I don't want to start a chip war. I might have to go back and rewrite the alarm clock in ASM and see the factor of 3 better code I get out of it compared to XC8 and I am not even particularly good at assembler lol.

author: MacAttak
author_rank: Unknown
timestamp: May 30, 2013, 12:59:11 am
content: I would never assume that manually written assembly would outperform straightforward high-level code (like C) by 3x unless you have a lot of time under your belt in assembly targeted to the specific platform.

author: C
author_rank: Super Contributor
timestamp: May 30, 2013, 02:32:53 am
content: This is just me,but if I was doing this, I might think8 segments = 8 pins6 digits = 6 pinsneed some additional ledsIf I use one more digit pin I could get 8 ledsneed switchesUse digit pins to mux digit counts worth of switches in to one pinneed more than one switch pressed at a time, so need a diode in-line with each switchso8 segments = 8 pins8 leds + 6 digits = 7 pins7 switches = 1 pinjust need 16 pinsIn display mux if i used a array containing segment data to update the display, I could display any thing possible and not have to change what section to display something else on the display. I could change the array data in the slow code section. Would make it easy to switch from time display to alarm time display to alarm time2 and to _____. I could even flash the display from slow code.but as I said this is just meC

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 30, 2013, 10:55:21 am
content: Quote from: MacAttak on May 30, 2013, 12:59:11 amIn the case of XC8 Free Edition a 12yr old can out optimize the compiler's asm generation. The general idea is with the XC8 free it looks like they run through the parsing and translation to ASM like any compiler would but then from there they go write to assembling it. They never even do a mediocre single pass optimization. The end result is tons of extraneous code that should not have to be there it is just there because of the way a compiler parses the code and maps it out to asm. The optimization is what cleans it up but again XC8 free does not even give you a basic single pass optimization. So in return your code size is 20 - 30% larger and can become unstable in timing because there are so many extra branches, goto's etc....You don't have to be a assembler programmer expert to look at the disassembly and go WTF is this compiler doing. Microchip really needs to give the compiler a single level optimization. Not asking for everything including the kitchen sink just a restricted set of optimization rules that will clean up some of the mess compilers make when compiling.----------------As for my pin count it is a 4 digit 7 segment multiplexed common cath display. I avoid the decimal and just run the colon full time off a resistor to V+ and cathode to gnd.7 segments = 7 pins4 digits = 4 pins1 LED = 1 pin (alarm on indicator)Buzzer = 1 pin5 tactile push spst = 5 pins (each one has a different function)Total pins = 18.(Keep in mind I am a total newb to designing circuits)My ATtiny only has 18 io if you turn off the reset pin (lose the ability to program chip without bootloader). So in reality you only have maybe 17 I/O pins or so to work with if you use only overflow interrupts and no external crystals. It would work but you lose a pin when you need to do the timer compare to get the timing down. So 16 pins total if using the more accurate hardware timer compare.Now if I ran off a 8 bit timer overflow like I do on the PIC but with a 1:8 prescale in stead of the 1:16 the pic gives me I would need one extra variable to manage the time due to needing the tick twice for the 4ms display update rate because at 1:8 the isr would go off every 2ms at a 1 uS instruction cycle time. Or you can still use the 16 bit timer and do the compare in software. This would still mean dropping 1 button or the led indicator out of the design.So the general idea would be to just convert the code to assembler so I can still use the 28 pin PIC and remove the variations from the timing due to the horribly optimized code. I am not saying the variations are large enough to break the project but they are still there and I would rather them not be because it is causing slight hiccups here and there. For instance there is a slight pause before the alarm goes off. Not all the time but occasionally. I have a hunch the little pauses here and there are coming from all the damn extraneous branching the compiler left in the code. Also before people say it NO the ISR is not overlapping or running too long but it does have a runtime variance depending on which interrupt it is servicing. (Downfall of only having a single ISR vector). Worse case is I still have the same issues if I port it over to assembler. In which case it is more or less the fault of the PIC ISR architecture causing me to have to do so many comparisons. Other then the occasional hiccup like I mentioned the clock works great.

author: croberts
author_rank: Regular Contributor
timestamp: May 30, 2013, 11:57:13 am
content: This is a topic I did some time ago for a real time clock/floating point display using a 20 pin PIC16F690 that may be of some interest. This design does not have an alarm function but may be of some interest to you.https://www.eevblog.com/forum/microcontrollers/networked-rtc-fp-display/I discovered a problem with the hardware blanking in floating point mode since that post that I fixed by adding a resistor and diode to the circuit. I have attached the revised schematic.

author: C
author_rank: Super Contributor
timestamp: May 30, 2013, 05:54:18 pm
content: crobertsNice design, Nice and simple.Pin 3 on the mpu, A very small change to dual purpose the board, I like it.=====Now using croberts circuit as a reference, If you have software space left, what changes could be made in the hardware to add more capabilities?One of my clocks has two buttons, one for forward in time, one for back in time, Holding the button down makes the time change faster.The Two buttons makes setting the clock nice and quick.Some other ideas also need more switches, and switches can bounce.So can you see some simple hardware changes to add more switches to croberts's circuit?What would happen if instead of connecting a switch across pins 1 & 2 of JP1 or JP2 and you connected it to pin 1 and the collector of one of the transistors T1 -T5?You could read the switch state just before you turned off the transistor it was connected to.There are four more transistors, so you could have 5 switches where you did have one.There is a problem with this if you press 2 or more switches at the same time as it would mess up the display, BUT there is a simple fix also of just putting a diode in-line with each switch. And with that diode in-line with switch you could have a Alarm-On switch with out problems.So the question becomes,Do you want more switches and if so, do you have a little space for software & is a switch and diode to much cost.Software switch DE-bounce.Now you could get cheaper switches that could bounce to cut costs if you had a little more software space. Some of the little mpu's are really lacking in ram space, so it could be a good idea to keep that use low when adding de-bounce. So if you had a byte sized integer, That was positive for switch pressed and negative for not, that would be nice When you read the switch status you could increase/decrease this integer until you reached a display scan count that would cover switch bounce time. So if this integer is 1 to display scan count, the switch is pressed and if 0 to -display scan count it is not pressed.So applying this to your clockQuotebecomes4 digits = 4 pins4 tactile push spst = 1 pins (each one has a different function) connected via a diode to digits pins.For a savings of 3 pins at the cost of a little more software and 4 diodes.EDIT:crobertsIn addition to switches, You could use this idea to add a zero cross ac input and/0r a one pulse per second input to your clock. Just gate the signal with the proper digit signal and diode in logic output.C

author: croberts
author_rank: Regular Contributor
timestamp: May 30, 2013, 06:30:16 pm
content: Thanks CGreat comments and ideas all!Dual purposing pins with hardware design is a great way to get more function out of fewer pins. I really like software debouncing for switches. For this design I've only used 1/4 of the available programming space and 1/5 of the available RAM and this includes software that supports the half duplex network so no problem there. I use R17 to set the time, works very smoothly (got the idea from the way time is set in my car radio with the volume control). When the circuit is used as a floating point display a 3 pin connector can be installed in place of R17 for stand alone display of an analog signal or the board can display values given it over the network.

author: C
author_rank: Super Contributor
timestamp: May 30, 2013, 08:57:25 pm
content: croberts:Row/Column scan of switches is a very common idea. The old radio shack TRS-80 used 8 address lines to drive the columns and read the rows with a data read. So a simple read from memory could check the switches. Note that in this case a read with all address lines set properly you could check if any switch was pressed. So 8 x 8 matrix of switches = 64 switches. Others did it with an 8 bit latch and a 8 bit input. You should note that most switch matrix do not have the diodes in-line with the switches so you could get a false report of a switch being closed when it is not. With the speed of a human and fast scanning, software can most times correct this. Common way to help prevent this is to put switches normally pressed with other keys on a separate row or column. Most likely your PC keyboard is a matrix.Sharing the row or column lines between display and input is also common.With a loss of a little flexibility your 5 digit lines could goto a 5 to 32 decoder for 32 digits or you could save 2 pins and use a 3 to 8 like the 74xx138 to drive your digits.And you could do the same with the switches with a 1 of 8 data selector.so your 8 segments + 5 for digits pins could become 8 segments + 8 digits + 8 switches for a saving of 1 pins and a cost of two chips.And as long as the digit scan rate is fast enough slow speed data could replace a switch.Now a hint: is this one of the pic's that can count an input faster then it's clock like the ones used in a pic based frequency counter?Have funC

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 31, 2013, 11:29:22 am
content: On my particular circuit I am debouncing the pins with hardware. Each button is pulled up with a 4.7k ohm resistor. Then there is a 10nF capacitor that gets charged. When you press the button it shorts draining the cap and reading the input off that gets rid of the bounce as you get a nice quick smooth drop. Works quite well.The dual purposing sound cool but I am not quite sure how you can use a output pin which changes the digits in the multiplexed display to read the button state unless you are rapidly changing it between input and output as you update each digit?Keep in mind I am new to all this only been messing around with the stuff for a few months so I think learning how to multi purpose pins like this would be a great learning experience so if you can direct me in impart on me some information about doing this it would be much appreciated. I would love to be able to iterate on the design of the alarm to use a smaller chip and avoid having to port over to assembler because of a bad free compiler.

author: Rerouter
author_rank: Super Contributor
timestamp: May 31, 2013, 11:57:12 am
content: this is the module i made up for my own AVR based project, at the time i needed the external interrupts to never be delayed, so made a latch out of 2 resistors and 2 transistors, and the thing works reliably,pretty much close your contact to ground, it turns on the pnp which holds on the npn latching the input until the internal 20K liftup on the pin is cycled resetting the latch, this way i could run a flag check even in rather large code loops, and even implement press and hold functionality by counting the time between the first attempt and last attempt to reset it,using sot23's you can get the design down to 10mm^2 per latch,

author: Rufus
author_rank: Super Contributor
timestamp: May 31, 2013, 01:46:30 pm
content: Quote from: blewisjr on May 31, 2013, 11:29:22 amYou have the digit common outputs which you are already driving one at a time. You connect switches between these outputs and one input. When you see the input driven it must be the switch connecting to the output you are currently driving that is closed.Closing multiple switches will short the outputs together. If that is an issue you can use series diodes to block the interaction.If your outputs don't need to be there all the time (LEDs for example don't need to be driven 100% of the time) you can flip them to inputs and read stuff.Attached is an old (nearly 20 years) design of mine for a hand held calculator type device. A 4x4 matrix keypad connects to JP1.

author: blewisjr
author_rank: Frequent Contributor
timestamp: May 31, 2013, 05:43:21 pm
content: Ah that is really cool thanks Rufus. This would easily allow me to use the 20 pin controller you learn something new all the time. Never thought of being able to multitask a pin that way.

author: C
author_rank: Super Contributor
timestamp: May 31, 2013, 07:58:02 pm
content: blewisjr:The ClockI think you said that you were doing an AVR based clock an switched to PIC so you had more pins.So have you asked your self if C works on an AVR and C works on a PIC why are you rewriting the C code?Even if you have stopped all work on the AVR based clock, Thinking of this could help you write better C code for the current clock.Your basic clock code needs to run once a second, so in C it could be the same. You just need to get the inputs from the AVR & PIC to look the same to the basic code. Then do same with the outputs.Now if you think about it, the more code you can put in the basic clock section, the less programing you have to do to have two clocks one on PIC and one on AVR.So on the input side you have the differences in reading the switches and how to get to the once a second running of basic clock. On the output side the display and other outputs.So as you write the basic clock, keep asking your self, is there a better way to do this part so it works on more clock hardware, Is there a better way to do this part so you do not need to make changes to the special C code for each clock.Display:I think you are using a multiplexed display on both clocks. What changes could you make to put more code in the basic clock section so that it is not needed to be done in the two different display sections. What changes could you make so that in the display part you have more C code in common between the clocks?Looking at your code I see that you have made a choice that instead of having separate digit to 7 segment decoder chip(a) to have the mpu do it.Lets look at the costs.4 pins for a digit output vs 7 or 8 pins for a segment output.digit to segment decoder chip vs SoftwareNote: by using a segment output can now display any segment output you want so you have more options.I see a lot on the good side for a cost of 3-4 pins.So Looking at your display code why are you using digit data an not segment data?Digit data does not change fast so it would be less work for the mpu to display segment data then create segment data from digit data on each scan. Think you said alarm, so if you want to switch to display alarm time or something else you have to change display code section again for each new display data. If display section just displayed an array of segment data, you could change segment data almost any where and display section would display it with no changes. If you wanted to change from a 4 digit clock to a 6 digit clock, you would just need to change the array size and how much of the array is displayed counter. So in basic clock section create segment data for the display section to use for display and The display section of code gets simpler.If you look at croberts hardware, there are 5 digits not 4. The colon for the display is treated as if it were a digit. You could also add a fake digit where you just treated 7-8 leds like they were a digit, This would just cost one pin instead of 7-8 pins. With display code displaying segment data, you would only need to change how many digits the display code was to display. You could build the segment data for the colon or the fake digit leds anywhere.mux Switch Input:If you look at my other post, I was not trying to get fancy, just using a normal input pin like you are using for one switch. In simple terms, some would call this a wired or gate. When a switch is pressed it connects that digit output pin to your input pin. When not pressed the pull-up or pull-down resistor sets the level the input pin sees. So the input pin is going up and down and to find out which switch caused the change you need to know which digit output pin is currently active. The function of your cap will need to be done in software.You currently have an input with a resistor pull up. First step is to look at your digit outputs. One of your digit outputs will be at a different state. If that state is high then the input pin of your mpu needs a pull down resistor connected. If that state us low then the pull up resistor you have will work. You will need to remove the cap as it's function will need to be done in software. You just connect a switch & diode in series between your current input pin and one of the digit output pins. Each switch to a different digit output pin. To keep things simple just put a diode in series with each switch, With out diode you could have problems when two switches were pressed at the same time or push-pull digit outputs.Now it takes time for the signal to go out a digit output pin and through the wires, switch, ____ so the best place to read the input pin is just before you change to the next digit on output. Unless you are short of ram, it easer in software to just pretend that you have a switch connected to each digit output pin even if you do not.for software debounce look for idea in previous postNOTE: A lot of mpu's can do a test for number = 0 with a lot less code then a test for number = ____ the same is true if the mpu is testing for less that or greater than.Long post and A lot of things so in summary:Display code just displays an array of segment data.Just before switching to a new digit it reads input pin for switches and stores the result in an array of switch data.read the input pin something like this.If input pin is 0 thenif abs (switch_array_data.)< bounce_count thenswitch array data. = switch array data -1If input pin is 1 thenif abs (switch_array_data.)< bounce_count thenswitch array data. = switch array data +1Note: the above gives you a byte size integer use in your main code. It will be a negative number for one switch state and positive number for other switch state.The above gives you two arrays for main clock code to use.Segment array containing what to display.Input switch array for current status of switches.If you think I goofed or have questions, AskC

author: blewisjr
author_rank: Frequent Contributor
timestamp: June 01, 2013, 11:05:01 am
content: I debounced the switches with smoothing caps on the current version of the clock. The main reason I was thinking of finding a way to convert the project back over to the AVR's I have is because I have come across a few issues with the XC8 compiler. I have to run it in free mode because I am poor and can't buy it. This is causing some occasional hiccups in the alarm clock. I think I tracked down the issue to some extraneous branching code that the compiler is generating which is causing about a 100ms fluctuation. So for instance when the clock changes and the alarm triggers there is about 100ms delay before you hear the buzzer start to beep. This delay/fluctuation is not always present it actually varies quite a bit which is why I think it is coming from the awful code that was generated. It is so awful I almost cried looking at the disassembly.Nothing serious but can easily be solved with a C compiler that will actually optimize the assembler output. Heck it can be easily solved by just coding the clock in assembler but I have been told numerous times by numerous people I should be using C. So it kind of puts me in a rock and a hard place with the PIC controllers no matter how much I love the chips themselves I will never have a reasonable C compiler to use.It really is no big deal the clock works great I would just like it to be even better because now that it is done I can iterate on it and learn more from it before I move onto my next project.

author: Rufus
author_rank: Super Contributor
timestamp: June 01, 2013, 11:19:13 am
content: Quote from: blewisjr on June 01, 2013, 11:05:01 amI doubt the compiler is an issue. You never mentioned if you found and understood the bug I said you had a couple of pages ago. Perhaps you have written a worse version of the same bug.

author: blewisjr
author_rank: Frequent Contributor
timestamp: June 01, 2013, 11:38:59 am
content: Was that the bug about the one pin being input but not implemented? If so that is long fixed because that button is there and implemented now. The large 20 - 100 ms execution gap has to do with the compiler. If I compile with the Pro free trial it goes away. There may be a way to eliminate it with heavy code refactoring possibly but I am not so sure. Just for the sake of it here is the current final code maybe you can spot where it is coming from.I apologize in advance if the code is hard to follow I tried to keep it organized but it kind of came to fruition iteratively.Code:#include <xc.h>// PIC16F1782 Configuration Fuse Settings// CONFIG1#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)#pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled)#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)// CONFIG2#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)#pragma config VCAPEN = OFF // Voltage Regulator Capacitor Enable bit (Vcap functionality is disabled on RA6.)#pragma config PLLEN = OFF // PLL Enable (4x PLL disabled)#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)#pragma config LPBOR = OFF // Low Power Brown-Out Reset Enable Bit (Low power brown-out is disabled)#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)// Global Variables// display digit lookup table// each segment pin needs to be turned on to make a digit// the circuit has the A-G pins hooked up to a I/O port where A-G = 0-6 respectively// the format for the bits is 0bGFEDCBAvolatile unsigned char digit_tbl = {0b00111111, // digit 00b00000110, // digit 10b01011011, // digit 20b01001111, // digit 30b01100110, // digit 40b01101101, // digit 50b01111101, // digit 60b00000111, // digit 70b01111111, // digit 80b01101111 // digit 9};unsigned char interrupt_cnt = 0; // number of times interrupt is called (250 = 1 second)unsigned char seconds = 0;// when clock is started it defaults to 12:00unsigned char hours = 12;unsigned char minutes = 0;unsigned char alarm_hours = 12;unsigned char alarm_minutes = 30;bit alarm_active = 0;bit alarm_set_enabled = 0;bit alarm_enabled = 0;bit snooze_active = 0;unsigned char alarm_hours_shad = 12;unsigned char alarm_minutes_shad = 0;// extracts the first digit from an integer ie. the 1 in the number 12unsigned char extract_dig1(const unsigned char* value){return *value / 10;}// extracts the second digit from an integer ie. the 2 in the number 12unsigned char extract_dig2(const unsigned char* value){return *value % 10;}// updates the display digitsvoid update_display(const unsigned char* h_val, const unsigned char* m_val){if (LATB0) {// Display 0 was updated last update digit 1LATB0 = 0; // turn off display 0LATB1 = 1; // turn on display 1LATC = digit_tbl[extract_dig2(h_val)]; // load the segments with the proper valuereturn;} else if (LATB1) {// Display 1 was updated last update display 2LATB1 = 0; // turn off display 1LATB2 = 1; // turn on display 2LATC = digit_tbl[extract_dig1(m_val)]; // load the segments with the proper valuereturn;} else if (LATB2) {// Display 2 was updated last update display 3LATB2 = 0; // turn off display 2LATB3 = 1; // turn on display 3LATC = digit_tbl[extract_dig2(m_val)]; // load the segments with the value 0return;} else {if (*h_val > 9) {// Either display 3 was updated last or no display is enabled (first interrupt cycle only)LATB3 = 0; // turn off display 3// on first run this is a wasted instruction but saves program spaceLATB0 = 1; // turn on display 0LATC = digit_tbl[extract_dig1(h_val)]; // load the segments with the proper valuereturn;} else {// we do not need to use display 0 so update display 1 insteadLATB3 = 0; // turn off display 3LATB1 = 1; // turn on display 1LATC = digit_tbl[extract_dig2(h_val)]; // load the segments with the proper valuereturn;}}}void interrupt interrupt_service(void){// Check for Timer0 overflow interruptif (TMR0IE && TMR0IF) {TMR0IF = 0; // clear interrupt flaginterrupt_cnt++; // update the interrupt called count// Update our time if necessaryif (interrupt_cnt == 250) {interrupt_cnt = 0;// One second has passedseconds++;if (seconds == 60) {seconds = 0;minutes++;if (minutes == 60) {minutes = 0;hours++;if (hours == 13) {hours = 1;}}}// is our alarm enabledif (alarm_enabled) {// is it time to trigger alarmif (hours == alarm_hours && minutes == alarm_minutes)alarm_active = 1; // trigger alarm} else {alarm_active = 0; // un trigger alarm}// Toggle alarm if triggeredif (alarm_active)LATA0 ^= 1;}// The overall display is a multiplexed 4 display 7 segment unit// displays corrispond to pins on PORTB in this circuit.// Display 0 is on LATB0 - Display 3 is on LATB3// In the circuit these displays are switched through NPN transistorsLATC = 0; // Clear all lit segments to prevent ghosting.// Update the appropriate display for this interrupt cycleif (alarm_set_enabled) {// we need to display the alarm so we can set the timeupdate_display(&alarm_hours, &alarm_minutes);return;} else {update_display(&hours, &minutes);return;}} else if (IOCIE && IOCIF) {if (IOCAF1) {IOCAF = 0; // clear interrupt flag// need to update to appropriate hoursif (alarm_set_enabled) {alarm_hours++;if (alarm_hours == 13)alarm_hours = 1;return;} else {hours++;if (hours == 13)hours = 1;return;}} else if (IOCAF2) {IOCAF = 0; // clear interrupt flag// need to update appropriate minutesif (alarm_set_enabled) {alarm_minutes++;if (alarm_minutes == 60)alarm_minutes = 0;return;} else {minutes++;if (minutes == 60)minutes = 0;return;}} else if (IOCAF3) {IOCAF = 0; // clear interrupt flagalarm_set_enabled ^= 1; // enable alarm set modereturn;} else if (IOCAF4) {IOCAF = 0;LATA0 = 0; // prevent buzzer always on bug// snooze function.if (alarm_active) {alarm_active = 0; // turn off alarmif (!snooze_active) {alarm_hours_shad = alarm_hours; // save current alarm hours statealarm_minutes_shad = alarm_minutes; // save current alarm minutes statesnooze_active = 1;}alarm_minutes = minutes + 5; // increment minutes by 5if (alarm_minutes >= 60) {alarm_minutes = 0;alarm_hours++;if (alarm_hours == 13)alarm_hours = 1;}return;}} else if (IOCAF5) {IOCAF = 0; // clear interrupt flagalarm_enabled ^= 1; // toggle alarm_enabled stateLATB4 ^= 1;LATA0 = 0;if (!alarm_enabled) {// return alarm statealarm_hours = alarm_hours_shad;alarm_minutes = alarm_minutes_shad;snooze_active = 0;}return;} else {IOCAF = 0;return;}} else {return;}}void main(void){/** OSCCON Register* bit 0 = Software PLL Enable* bit 6:3 = Internal oscillator frequency* bit 2 = Unimplemented* bit 1:0 = system clock select*/OSCCON = 0b01101000; // 4 MHZ oscillator frequency clk selected via FOSC fuseANSELA = 0; // ensure PORTA analog inputs are disabledANSELB = 0; // ensure PORTB analog inputs are disabledTRISA = 0b00111110; // set RA0 as output RA1 - RA5 as inputTRISB = 0; // set PORTB data direction to outputTRISC = 0; // set PORTC data direction to outputPORTA = 0;LATB = 0;LATC = 0;TMR0 = 0; // clear the Timer0 counter and prescaler// Enable interrupt on change falling edge for PIN RA1 - RA5IOCAN = 0b00111110;/** INTCON* bit 7 = Global Interrupt Enable* bit 6 = Periferal Interrupt Enable* bit 5 = Timer0 Overflow Interrupt Enable* bit 4 = INT External Interrupt Enable* bit 3 = Interrupt-On-Change Interrupt Enable* bit 2 = Timer0 Overflow Interrupt Flag* bit 1 = INT External Interrupt Flag* bit 0 = Interrupt-On-Change Interrupt Flag*/INTCON = 0b10101000; // enable global interrupts, enable Timer0 interrupt and pin change interrupts/** OPTION_REG Register* bit 7 = Weak Pull-Up Enable* bit 6 = Interrupt Edge Select* bit 5 = Timer0 Clock Source Select* bit 4 = Timer0 Source Edge Select* bit 3 = Prescale Assignment* bit 2:0 = Prescale Rate*/OPTION_REG = 0b11010011; // bit 7:6 is not used by Timer0// set timer to use internal instruction clock (FOSC/4)// bit 4 is not used and prescale Timer0 by 1:16// this will give us a overflow interrupt of aprox. 4mswhile (1);}

author: Rufus
author_rank: Super Contributor
timestamp: June 01, 2013, 02:29:09 pm
content: Quote from: blewisjr on June 01, 2013, 11:38:59 amThe bug is avoided by moving the alarm time check out of the main loop. I looked at the program and don't see anything wrong. I did note it won't keep time because the interrupt runs at 4.096ms not 4, but, the internal oscillator isn't accurate enough to keep time anyway.I attach the posted program compiled in Pro mode, doubt it will function differently.

author: blewisjr
author_rank: Frequent Contributor
timestamp: June 01, 2013, 02:45:31 pm
content: Yea it does not keep time perfectly accurately due to using the internal oscillator there is a small variance from that which is unavoidable without using a external oscillator but I probably can compensate in software for it but not a major concern of mine atm. What I am probably seeing is the internal oscillator error range and not actually the branching now that I think about it.

author: croberts
author_rank: Regular Contributor
timestamp: June 01, 2013, 02:46:01 pm
content: You can get the 4mS interrupt interval by adding 5 to the current value in tim0 and then loading that value into tim0 at the beginning of the interrupt service routine. This will effectively cause tim0 to rollover after 250 counts instead of 255 counts. The addition is necessary to compensate for the counts already accumulated before the interrupt is serviced. You can compensate somewhat for the oscillator inaccuracy by adding or subtracting a fixed number of seconds every hour depending on the time running slow or fast.

author: blewisjr
author_rank: Frequent Contributor
timestamp: June 01, 2013, 03:03:59 pm
content: Quote from: croberts on June 01, 2013, 02:46:01 pmYea this is what I was thinking about handling it in software. I am probably going to move onto another project now because I really don't want to go through all the effort of designing a PCB/housing for it. I think it would be rather tough to put it on a perfboard cleanly. I got what I wanted out of the project. I completed the project for one and for two I learned so much from it. Which is something that would not have happened if I did not start it in the first place. I think it was a good first project for me.

author: C
author_rank: Super Contributor
timestamp: June 01, 2013, 03:59:24 pm
content: Learning is great, you might want to ask your self these.Could you learn some more now that would make the next project easer?Could you make some changes that lower the amount of computing work the mpu is doing?Could you make some changes that would let you use parts of this code for other projects?Is there simple code changes that would make the code less breakable and easer to change?C

author: Mr Smiley
author_rank: Frequent Contributor
timestamp: June 01, 2013, 06:42:47 pm
content: 5+3 cos TMR0 is stopped for 3 cycles when it is added to

author: C
author_rank: Super Contributor
timestamp: June 01, 2013, 06:57:53 pm
content: QuoteNot much time involved here but for a short time you do have the current digit displaying the previous digits segments.if (LATB0) {// Display 0 was updated last update digit 1LATB0 = 0; // turn off display 0LATC = digit_tbl[extract_dig2(h_val)]; // load the segments with the proper valueLATB1 = 1; // turn on display 1return;I think would be better.C