PIC 12F629 Question.

JohnInTX

Joined Jun 26, 2012
4,787
Post #8 is a method you can do your button press. Just poll in a loop for one second. It is really the number of instructions that equate to a second. This is what time delays do. They simply execute instructions in a loop.
They also consume 100% of the CPU time, eventually eating your sack lunch. A better way is to use an interrupt-driven timer chain which we can get to pretty easily. Burning 1E6 Tcyc of processing to look at a switch is maybe not the best way to go.

IMHO, I would really like to see the blocking-delay construct go away forever. @joeyd999 would, too.
You get three and its a movement. How 'bout it?
 
Last edited:

spinnaker

Joined Oct 29, 2009
7,830
Other code could be executed in the loop. There is nothing that says code has to be blocked while detecting the switch press.
 

JohnInTX

Joined Jun 26, 2012
4,787
Other code could be executed in the loop. There is nothing that says code has to be blocked while detecting the switch press.
True enough but that means recalculating the loop each time the other code changes. Branches and other input-determined things can make that a formidable task. I've been there.
Sorry, and nobody has to listen to me either, but I don't like making timing a side-effect of execution.

Been there. Done that. Coded the interrupts.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
I dunno but I tend to agree with John as delay halts the PIC and I dunno I do not like delays. That is why I asked. I know John will have an answer.
It took two days to get the Data out so I believe I will presume work with in a day or two.
@spinnaker
Sorry but I do not see a code at post #8
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Last night I made a circuit with 5 led strips so I can hook it up to the headers of EasyPic.
Two logic level Mosfets. One driving 1 strip at 12V 250mA and other driving 4 strips at 12V around 1 amp. So I can test the code as I intended
 

djsfantasi

Joined Apr 11, 2010
9,163
You can code the loop and switch state detection, so that one does not disrupt the other. I'd put the check button code in a function and call it in the loop. I'd then save the result AND OR IT WITH THE PREVIOUS VALUE. That way the code can act on the button press when it's convenient. And at that time, reset the value of the saved button press. I've used this technique.
 

joeyd999

Joined Jun 6, 2011
5,283
Since hardly anyone ever posts good debounce code, I'm going to post mine here. Yes, it in .asm. And for 18F...but it could be ported to C and 12F.

Note that a button must be pressed for a full 32ms (4 scans) before it is considered down, and released for a full 32ms before it is considered up. The code is non-blocking. It is called each pass of the main loop.

It scans up to 8 buttons at once, tied to portb. I also have a version for multiplexed keypads.

TC8ms is a flag that is set for one main loop pass every 8 ms. This is generated in my 'heart beat' interrupt running continuously off of timer 0.

This is only 1/2 of what I actually do. The other half sets flags for things like press and hold, press and release, press and repeat, double press, etc. But that's not important here.

Edit: I use fsr0 to access the debounce table in a different bank -- it's more compact than bank switching. Not necessary if all registers are in the same bank.

One last time: how do I set the damn tab width in a CODE block?

Code:
;*****************************************
;** GETKEY -- Process keys every 8 ms   **
;**   by JoeyD999 posted 11/3/15        **
;*****************************************

getkey    retbc    TC8ms            ;process kbd row every 8 ms (total 32 ms debounce time)

;shift debounce table

    lfsr    0,kbddb+2        ;point to bottom of keyboard debounce table

    movff    postdec0,kbddb+3    ;shift entries
    movff    postdec0,kbddb+2
    movff    indf0,kbddb+1

;fsr0 -> top of debounce table

gktop    comf    portb,w            ;get new keypad pattern
    andlw    keymsk            ;mask actual keys
    movwf    indf0            ;and save new entry

;now, test buffer pattern -- all 1s = pressed/all 0s=released/mixed no action

    movf    postinc0,w        ;get all the 1's
    andwf    postinc0,w
    andwf    postinc0,w
    andwf    indf0,w            ;w has all keys held for 4 passes

    iorwf    keyhld,f        ;set them in keyhld

    movf    postdec0,w        ;get all the 0's
    iorwf    postdec0,w
    iorwf    postdec0,w
    iorwf    indf0,w            ;w has all keys released for 4 passes

    andwf    keyhld,f        ;clear them in keyhld

    movfw    keyhld            ;get current pattern
    xorwf    keylst,w        ;compare to last full scan
    andwf    keyhld,w        ;keep only those pressed since last
    movwf    keychg            ;flag new key presses this scan
    movff    keyhld,keylst        ;save new pattern as last

    return
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
C:
#define WhiteMask 0x01
#define OFFmask (~0x01)
#define PWMbaseK 255
#define Whitepct 50

unsigned char PWMbase;
unsigned char WhiteDUTY;
unsigned char OutMask;

void outPWM()
{
     OutMask &= OFFmask;
  
     if(WhiteDuty){
                   WhiteDuty--;
                   OutMask |= WhiteMask;
                   }
      GPIO = OutMask;
}


void main()
{
      CMCON = 0x07;           //Comparator off
      OPTION_REG = 0x97;
                              // b'1001011'
                              //GPPU = 1   --> Pull ups disabled
                              //INTEDG = 0 --> Interrupt on Falling edge of GP2
                              //TOCS = 0   --> Internal Inst Cyc clock
                              //TOSE = 1   --> TMRO Increment on Hi to lo
                              //PSA = 0    --> Prescaler to TMRO 0
                              //PS2:PSO = 1--> 1:256 Prescaler Rate
      TRISIO = 0x3C;          //GPO & GP1 AS Out rest In
      GPIO = 0;               //Clear Outputs
      OutMask = 0;            //Clear OutMask
      PWMbase = PWMbaseK;     // Load PWMbase with PWMbaseK value
  while(1)
{
      if(PWMbase)
             {
             PWMbase--;
             outPWM();
             }
        else
            PWMbase = PWMbaseK;
            WhiteDuty = (Whitepct * PWMbaseK)/100;
}
}
This is what I got so far.
I am having an issue building this. Wasted an hour or so still could not find the problem. See the picture of the error. Can you please point me to the issue
Problem.png
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Never mind..I managed to build it.. seems I made a boo boo some where in the project setting.So I, Copy pasted the whole code on to a new project and it builds OK but I can test it tomorrow night.
So I will wait for comments.
 

JohnInTX

Joined Jun 26, 2012
4,787
I think that covers the basic PWM. Its running unscheduled because its still in the while loop. But yeah. But, you should be able to change the value of WhiteDUTY from 0-100, rebuild and see the change in the LED intensity.
When that works, change Whitepct to a variable and make it feed the PWM. Add some dummy code in the init to load Whitepct to some duty cycle. Rebuild with different init values to confirm that the PWM is using it properly.

Note that since Whitepct is now a variable, it has an implied range of 0-100% but WhiteDuty has the range 0-PWMbaseK. This (as well as the PIC peripheral PWMs) express the duty cycle as a fraction of the base count, not as 0-100%. i.e. for 50% duty cycle, if the base is 256, the duty register counts 128. If the base changes to 80, the duty register counts from 40 etc.

In this hard-coded version the line WhiteDuty = (Whitepct * PWMbaseK)/100; calculates the value at compile time so there is no real overhead. You'll have to make a choice. To express WhiteDuty internally as 0-100%, your code will have to do the calculation that maps 0-100% to 0-PWMbaseK. That's nice but an unnecessary complication (and use of codespace and precious time).

I would instead use the raw 0-PWMbaseK range for WhiteDuty and add/subtract several counts from it on each switch push. For example, if you wanted each push add 10% to the duty cycle you could add 10 to Whitepct then use that to run the WhiteDuty = (Whitepct * PWMbaseK)/100 calculation OR, you could calculate what 10% is of PWMbaseK (in this case 10% of 256 about 26 counts ) and just add 26 to WhiteDuty on each push. Same for subtract. Keep track of underflow and overflow, of course.

Reducing calculations by doing as much as possible in the design as opposed to making the PIC do complex calculations is a good technique to master as you get into more complex stuff. Never forget that even if its simple to code a complex calculation in C, the PIC still has to generate the code in assembler to do it. Add/subtract a byte value is much cheaper than multiply/divide.

Once you are happy with all of that, we'll schedule it with the timer.
Then we'll do the button(s).
Carry on!
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
Since hardly anyone ever posts good debounce code, I'm going to post mine here.
Interesting method, if not a bit clumsy. Wouldn't it be simpler/smaller/faster to use cumulative 'change' bits to toggle the debounced state latch?
Code:
;
;  8-ms sample intervals (24 to 32 millisecond debounce time)
;
sample
        movf    kbddb+2,W       ; shift 'delta' accumulator
        movwf   kbddb+3         ;
        movf    kbddb+1,W       ;
        movwf   kbddb+2         ;
        movf    kbddb+0,W       ;
        movwf   kbddb+1         ;

        comf    GPIO,W          ; sample active lo switches
        andlw   b'00000011'     ; on the GP1..GP0 pins only
        xorwf   keylst,W        ; changes, press or release

        movwf   kbddb+0         ; get cumulative delta bits
        andwf   kbddb+1,W       ;  "
        andwf   kbddb+2,W       ;  "
        andwf   kbddb+3,W       ;  "
        xorwf   keylst,F        ; update switch state latch
        andwf   keylst,W        ; filter out 'release' bits
        movwf   keychg          ; set "new press" flag bits
Not bad when you consider an alternate method using vertical counters isn't much smaller or faster;
Code:
;
;  8-ms sample intervals (24 to 32 millisecond debounce time)
;
sample
        comf    GPIO,W          ; sample active lo switches
        andlw   b'00111000'     ; on the GP5..GP3 pins only
        xorwf   latch,W         ; changes, press or release
        movwf   vmask           ; save temporarily
;
;  reset inactive vertical counters
;
        andwf   vcnt0,F         ; reset inactive counters
        andwf   vcnt1,F         ;  "
;
;  increment vertical counters
;
        comf    vcnt0,F         ; inc vertical counters
        movf    vcnt0,W         ;  "
        xorwf   vcnt1,F         ;  "
;
;  debounced state logic (4 samples at same state in 24-msec span)
;
        movf    vmask,W         ; changes, press or release
        andwf   vcnt0,W         ; check for overflow
        andwf   vcnt1,W         ;   "
        xorwf   latch,F         ; update switch state latch
        andwf   latch,W         ; filter out 'release' bits
        movwf   flags           ; set "new press" flag bits
 
Last edited:

joeyd999

Joined Jun 6, 2011
5,283
Interesting method, if not a bit clumsy.
In fact, what was really clumsy was that, in my haste to extract the useful parts of my code, I neglected to include the code that clears 'keychg' register upon entry to the routine, prior to the 'tc8ms' test. Without it, the 'keychg' register is persistent through each main loop -- resulting in a mess. The 'keychg' should hold only new keys pressed for a single main loop pass. Here is the corrected code:

Code:
;*****************************************
;** GETKEY -- Process keys every 8 ms   **
;**   by JoeyD999 posted 11/4/15        **
;*****************************************

getkey clrf    keychg             ;clear new keypresses for each main loop pass

       retbc    TC8ms              ;process kbd row every 8 ms (total 32 ms debounce time)

;shift debounce table

       lfsr     0,kbddb+2           ;point to bottom of keyboard debounce table

       movff    postdec0,kbddb+3   ;shift entries
       movff    postdec0,kbddb+2
       movff    indf0,kbddb+1

;fsr0 -> top of debounce table

gktop  comf     portb,w             ;get new keypad pattern
       andlw    keymsk             ;mask actual keys
       movwf    indf0              ;and save new entry

;now, test buffer pattern -- all 1s = pressed/all 0s=released/mixed no action

       movf     postinc0,w         ;get all the 1's
       andwf    postinc0,w
       andwf    postinc0,w
       andwf    indf0,w           ;w has all keys held for 4 passes

       iorwf    keyhld,f          ;set them in keyhld

       movf     postdec0,w         ;get all the 0's
       iorwf    postdec0,w
       iorwf    postdec0,w
       iorwf    indf0,w           ;w has all keys released for 4 passes

       andwf    keyhld,f          ;clear them in keyhld

       movfw    keyhld            ;get current pattern
       xorwf    keylst,w          ;compare to last full scan
       andwf    keyhld,w          ;keep only those pressed since last
       movwf    keychg            ;flag new key presses this scan
       movff    keyhld,keylst     ;save new pattern as last

       return
Wouldn't it be simpler/smaller/faster to toggle cumulative 'change' bits into the debounced state latch? ...consider an equivalent...
But your code is not equivalent. Your history is lost the instant a '1' on the port is detected (button presumably released). In 99% of cases, this is ok and correct -- and the way I used to write debounce code years ago.

But those clumsy Chinese have a habit of manufacturing cheap keypads that are not as reliable as the expensive American types. A weak press can (and has, in my experience) cause a string of make/breaks resulting in erratic operation. Note that my code requires both a 32ms press to activate and a 32 ms release to deactivate. Any chatter is eaten by the debounce table.

This works 100% of the time, even with aged, cheap keypads and dirty switches. It is well worth the extra code and registers.

*** Edit *** -- Time to eat some crow: @MMcLaren is absolutely correct. His code performs, for all intents and purposes, exactly the same as mine, while saving 1 register and a few machine cycles. My mistake is that I didn't realize his vertical counters required initialization to 0xFF, and this threw off the simulation in my head. It is good code. I shall leave my erroneous conclusion intact above, as proof that all of us make stupid embarrassing mistakes. Even me. Apologies.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Note that my code requires both a 32ms press to activate and a 32 ms release to deactivate. Any chatter is eaten by the debounce table. This works 100% of the time, even with aged, cheap keypads and dirty switches. It is well worth the extra code and registers.
Debouncing both ways also accommodates the variety of switches, sensors etc. found in harsh industrial environments. Even solidly closed switches can give flaky outputs due to vibration/bumps etc. I always debounce both ways after going through many alternatives approaches over the years.
 

JohnInTX

Joined Jun 26, 2012
4,787
@JohnInTX
Ummm..you are forgetting that I like to vary the PWM using a rotary encoder.
No problem. Well written code breaks functions down into individual modules with clearly defined functions. Once the PWM is written, it will accept Increase / Decrease commands. Buttons or encoder will be a module that just generate the increase/decrease commands without any mods necessary to the PWM code.
 

MMcLaren

Joined Feb 14, 2010
861
In fact, what was really clumsy was that, in my haste to extract the useful parts of my code, I neglected to include the code that clears 'keychg' register upon entry to the routine, prior to the 'tc8ms' test. Without it, the 'keychg' register is persistent through each main loop -- resulting in a mess. The 'keychg' should hold only new keys pressed for a single main loop pass.
What are you talking about? Your 'keychg' variable isn't persistent. You overwrite it each time through the routine.
But your code is not equivalent. Your history is lost the instant a '1' on the port is detected (button presumably released). In 99% of cases, this is ok and correct -- and the way I used to write debounce code years ago.
Nonsense!
But those clumsy Chinese have a habit of manufacturing cheap keypads that are not as reliable as the expensive American types. A weak press can (and has, in my experience) cause a string of make/breaks resulting in erratic operation. Note that my code requires both a 32ms press to activate and a 32 ms release to deactivate. Any chatter is eaten by the debounce table.
Actually, four samples at the same state at 8-msec intervals is a span of 24-msecs so your code requires a minimum steady 'press' or 'release' state of between 24 and 32 msecs. BTW, my code also debounces both the 'press' and 'release' states.
This works 100% of the time, even with aged, cheap keypads and dirty switches. It is well worth the extra code and registers.
My routines work just as well, even when using a pair of bare wires. In this case, if the extra code and registers don't improve performance or add features they're simply unnecessary extra code and registers (grin).

Cheerful regards, Mike
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
Excuse me for getting side-tracked providing Joey' with a "good" debounce example.

In this case, the example is probably not the right choice for this application. Implementing "short" and "long" button presses implies that we need to detect and act on both "new press" and "new release" switch states while timing the duration of the switch press. Here's one method which may or may not lead to a good solution...

Code:
  char swnew = 0;              // switch sample var
  char swold = 0;              // switch state latch
  char flags = 0;              // switch new press flags

  #define norm 0               // flag for "short" press
  #define long 1               // flag for "long" press

  #define newpress    swnew.3 == 1 && swold.3 == 0
  #define newrelease  swnew.3 == 0 && swold.3 == 1


  while(1)                      //
  { bam();                      // do 25-msecs worth of PWM
    swold = swnew;              // update switch state latch
    swnew = ~gpio;              // sample active lo switches

    if(newpress)                // if "new press"
    { swtmr = 1000/25           // start 1-second timer
    }                           //

    if(newrelease)              // if "new release"
    { if(swtmr)                 // if not 1-second timeout
      { flags.norm ^= 1;        // toggle "short" flag and
        swtmr = 0;              // clear 1 second timer
      }                         //
    }                           //

    if(swtmr)                   // if timer running
    { swtmr--;                  // decrement it
      if(swtmr == 0)            // if timed out
      { flags.long ^= 1;        // toggle "long" flag
      }                         //
    }                           //

    if(flags.long)              // if long new press
    {                           //
    }                           //

    if(flags.norm)              // if short new press
    {                           //
    }                           //
  }                             //
Additional code would be required to read the rotary encoder and set "up" and "down" switch flags...

If PWM is handled in an ISR then another method might be used to provide the 25-msec switch sample interval in the main loop...

Hope this helps...
 
Last edited:
Top