PIC 12F629 Question.

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
I see that now.
C:
unsigned char CurrentDuty; // for duty cycle change test (beeps)
I have commented out the encoder beeping. Silly me. :p

The EasyPIC7 requires the PIC to generate the tone. It has a transistor to drive the buzzer. The diagram is a copy of the easy pic buzzer driver.
I do not think PIC is hanging. Just the flash period is prolonging the beep. A long beep is more than the flash period.
But the Mike's method ( I did in #70) does not affect the flash. Power on, flash is there but neat little 2 short beeps.
This thing is way over my head. I am still sitting blank looking at it trying to understand what does what.
I dunno, I am lucky to write this looking at your examples and get it working as you guys are suggesting.

I guess we can figure this out but it is not a main issue.

In order to keep the PWM off at power up what do you suggest ? I guess normal method is out since the IRQ is handling most of the stuff.
Will it do if I disable the Interrupts during OFF times and enable them when I switch it oN. ( OFF, ON is meaning not power down but ON OFF using switch - a 1 sec press )
 

JohnInTX

Joined Jun 26, 2012
4,787
In order to keep the PWM off at power up what do you suggest ? I guess normal method is out since the IRQ is handling most of the stuff.
Will it do if I disable the Interrupts during OFF times and enable them when I switch it oN. ( OFF, ON is meaning not power down but ON OFF using switch - a 1 sec press )
You don't want to do that. This thing is coordinated using interrupt - and it will work. I changed my PWMduty to 0 on power up and no flash.

My best guess is that somewhere along the line our various approaches got scrambled and are interfering with each other. Post your code and I can put it in MikroC and give it a look-see. I've been running X because I don't have a debugger compatible with MikroC but I can get around that, I think.

This thing is way over my head. I am still sitting blank looking at it trying to understand what does what.
Yeah.. sorry.. We kind of dropped a couple of loads on you all at once. That's what you get for being smart and competent - you get the serious stuff. Its working here. My best guess is that somewhere along the line our various approaches got scrambled and are interfering with each other. Post your code and I can put it in MikroC and give it a look-see.

Even though it seems more complicated, non-blocking, interrupt driven code is the way to go for things like this and its really easier to manage in the long run. It also will equip you to tackle bigger projects with ease but it takes a different line of thinking and takes awhile to get used to. Instead of seeing beep on - wait - beep off in the code flow, now you have some 'black box' that you throw an integer at. The attraction is that if you decide to change the beep pattern, you don't have to re-code the black box - just give it different data to process. As your programs get bigger, you'll appreciate the concept. Lots of little black boxes that will do your bidding - flash, run, steer, beep, as you send data commands to them. Eventually, you get to the point where you don't care how a function is done, just send a command to the box and you are done. You don't have to worry about timing, interference with other modules etc. Nice.

In your early posts, you correctly identified the problems associated with using _delay(). I'm pretty sure that MikroC's beep library also delays the code for the beep time while generating the PWM for the beeper. OK if that's the only thing you want to do. Not so much if you also want to maintain encoders and the LED PWMs. So we are on the right track.

I understand your frustration. Don't worry, it will work.
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
You don't want to do that. This thing is coordinated using interrupt - and it will work.
I did and it went haywire :p. So back to the old one.

I changed my PWMduty to 0 on power up and no flash.
I will check this part.

Yeah.. sorry.. We kind of dropped a couple of loads on you all at once.
That's an understatement. :D

That's what you get for being smart and competent - you get the serious stuff.
I am not that smart but I like the serious stuff.

No worries. No frustration here. This is a learning curve and you will find me patient, but in the end I will have a useful light that I alwayys wanted. :rolleyes:

The MikroC beep uses delays and I have fiddled with it. Not good for this project.
Your approach is very good even though I do not understand most of the code I get how everything works some what.

The code below is now running.
As said before the beep is long (short beeps) during flash.
No other issue so far.
All it needs now is ON OFF and output toggling.

C:
/*================================================================================
****** PIC 12F629 CONFIGURATION ******
Set at Project setting in MikroC ~ Config = 0x3194
Internal Osc at 4Mhz. IO on GP4 and GP5
WDT Disabled
PWRT Disabled
MCLR Disabled
Brown Out Disabled
Code Proection Disabled
Data Protection Disabled
--------------------------------------------------------------------------------
****** PIC IO Details ******
GP0 - Single Strip Output.
GP1 - 4 Strip Output.
GP2 - Buzzer Output.
GP3 - Switch Input.
GP4 - Encoder Input, Active Low, 10K pullup.
GP5 - Encoder Input, Active Low, 10K pullup.

// IO is shadowed i.e. set/clear bits in GPIOimg, NOT on GPIO directly.
// The interrupt will write the port.
// This avoids having to turn off the IRQ when main does IO
*/
//------------------------------------------------------------------------------
/******* PROGRAM CONFIGURATION  ***************/
#define TMR0period_us  250      // how many usec per TMR0 irq
#define ENdebounce_ms  5        // how many ms to debounce the encoder
#define PWMbaseN 50             // how many TMR0 IRQs in one PWM cycle
#define PWMchangePerClick 1     // how much to change duty cycle each encoder click
#define InitialPWMDutyK 0       // PWM on power up (actual counts, not percent)
                                // 0 for Min. and 100 for Max.
#define ENphaseAmask  0x10      // Phase A is on GP4
#define ENphaseBmask  0x20      // Phase B is on GP5
#define Beepermask    0x04      // Buzzer out GP2
#define PWMoutmask    0x03      // PWM out is GP0 & GP1
#define ENCODER_MASK (ENphaseAmask | ENphaseBmask) // bits with encoder inputs
//*************** CALCULATED CONSTANTS  *******************************
#define TMR0reload 256-TMR0period_us+3   // what to reload TMR0 to
#define ENdebTIKS ((ENdebounce_ms *1000) / TMR0period_us)
#define PWMdutyKinit ((InitialPWMpct * PWMbaseK)/100)
#define msec5PSset 5000/TMR0period_us
//********************** VARIABLES  **********************************
// Encoder
unsigned char curENCODER,newENCODER,lastENCODER,ENdebK,ENstate,GPIOimg;
bit NEW_ENCODER_STATE;
// Switch and Buzzer
unsigned char msec5PreScaler;
unsigned char BeepTimer_5ms;
bit soundBEEPER;
// Beeper pattern.  Bit mapped variable gets shifted right each BeepTIK.
// Iff LSbit==1 beeper sounds for one BeepTIK.  If 0, beeper is off for that TIK.
// By scattering bits in BeepPattern, patterns and different beep times can be
// generated.
typedef unsigned int BeepPatternType;
BeepPatternType BeepPattern;
//unsigned char CurrentDuty;  // for duty cycle change test (beeps)
#define BeepCtrlmask ((BeepPatternType) 1)   // tests LSbit in char or int or long
#define BeepTIK_ms  50     // ms per tik
#define BeepTimerSet (BeepTIK_ms / 5)
// Beeper patterns for int
//#define Beep_OFF 0b0000000000000000     // no beep (copy/paste for new pattern)
#define Beep_Short1 0b0000000000000001  // 1 short beep
//#define Beep_Short2 0b000000000000101  // 2 short beeps
#define Beep_Long1 0b0000000000001111   // 1 long beep
//#define Beep_Long2 0b0000111100001111   // 2 long beeps
//#define Beep_Alarm 0b0101010101010101   // 8 short beeps
//#define Beep_Shave_AndA_Haircut 0b1010010110101011
// PWM
unsigned char PWMbaseK,PWMdutyK,PWMduty;

/*********************** INTERRUPT SERVICE  **************************
Interrupts when TMR0 rolls over. Reloads it to trim period then
processes items below.
Timer 1 is unused.
Note that IO is done here.  No direct outputs to GPIO in main is allowed
unless interrupts are disabled - not super good when running a software PWM*/

void interrupt()
{
    TMR0 += TMR0reload;     // Reload TMR0 to trim period
    INTCON.T0IF = 0;        // Clear IRQ flag

    //------------------ SERVICE PWM  --------------------------------
    // PWMbaseK counts PWMbaseN interrupts to make the PWM period
    // The PWM output will be high for the fraction of this period
    // determined by PWMdutyK.
    GPIOimg &= ~PWMoutmask;     // assume it will be 0
    if(PWMbaseK){
        PWMbaseK--;
        if(PWMdutyK){
           PWMdutyK--;
           GPIOimg |= PWMoutmask;
        }
    }
    else{
        PWMbaseK = PWMbaseN;
        PWMdutyK = PWMduty;
        GPIOimg |= PWMoutmask;
        }
    GPIO = GPIOimg;         // update port
    //------------------- SERVICE BUZZER--- ---------------------------
    if(soundBEEPER)
        GPIOimg ^= Beepermask;       // 50% duty PWM at TMR0 rate
    else
        GPIOimg &= ~Beepermask;    // else, beeper output 0

    GPIO = GPIOimg;                 // update port

    //------------------- SERVICE ENCODER  ---------------------------
    // Reads switch and compares current value to last valid one.
    // If its different, it attempts to debounce it by counting
    // each time it enters the interrupt.  When the switch is stable
    // for ENdebTIKS, the previous and current value are combined into
    // one 4bit value which indicates which way the switch turned.
    // See the encoder processor in main for more details.
    // Note that the switch inputs can be on any GPIO input.  This sorts that
    // out.

    curENCODER = (GPIO & ENCODER_MASK);
    if(ENdebK == 0){        // 0 means not debouncing any changes
        if(curENCODER != lastENCODER){  // but it has just changed so..
            ENdebK = ENdebTIKS;
            newENCODER = curENCODER;    // save value to compare
        }
        else;   // else its not changed, do nothing
    } // was not debouncing, debK >0 iff it just started a new one
    else{  // it is currently debouncing
        if(curENCODER != newENCODER){  // changed during debounce!
            ENdebK = ENdebTIKS;         // reset debouncer w/new value
            newENCODER = curENCODER;
        }
        else{   // still same value
            if(--ENdebK == 0){ // dec and test debounce counter iff Z..
                lastENCODER=newENCODER; // update 'last' value for next click
                ENstate = (ENstate << 2);       // shift is to was
                ENstate &= 0b00001100;  // clear out old and unused bits
                                        // then combine new inputs to two bits
                if(newENCODER & ENphaseAmask) ENstate |= 0x01;
                if(newENCODER & ENphaseBmask) ENstate |= 0x02;
                NEW_ENCODER_STATE = 1;
            } // debounce complete-new ENstate
        }// encoder value was same during debounce
    }// its currently debouncing
    //---------------------- SERVICE DERIVED TIMERS  ---------------------------
    // Prescaler counts msec5PSset interrupts to make a 5msec period.
    // From that, as many timers can be created as desired.  Set the timer
    // to the number of IRQ periods to time.  The timer will run to 0 and stop.
    // Poll the timer, when its 0, time is out. Do something, reload and go.
    if(msec5PreScaler == 0){            // iff prescaler ran out..
        msec5PreScaler = msec5PSset;    // reset it and run timer(s)
        if(BeepTimer_5ms) BeepTimer_5ms--;
    }
    else
      msec5PreScaler--;
}// IRQ service

//********************* INCREMENT AND DECREMENT  *****************************
// Called by the encoder switch state decoder.  Increments/Decrements PWMduty
void increment()
{
    if ((PWMduty + PWMchangePerClick) >= PWMbaseN)
        PWMduty = PWMbaseN;
    else
        PWMduty += PWMchangePerClick;
}

void decrement()
{
   if (PWMduty < PWMchangePerClick)
           PWMduty = 0;
   else
       PWMduty -= PWMchangePerClick;
}
//********************* BEEPER CONTROL  *************************************
// Sets pattern, clears timer to force immediate service/terminate current one
void setBeepPattern(BeepPatternType p)
{
    BeepPattern = p;
    BeepTimer_5ms = 0;
}

//************************ MAIN  *********************************************
void main() {                 //
    INTCON = 0;         // Clear all IRQs.
    PIE1 = 0;           // Clear all Interrupts.
    OPTION_REG = 0x88;  // No Pullups, TMR0 runs at Tcyc.
    CMCON = 0x7;        // Comparators off.
    //---------------- INIT HARDWARE IO  ----------------------
    GPIOimg = 0x38;     // this must agree with the next line
    GPIO = 0x38;        // Init the I/O f/fs first.. then TRIS
    TRISIO = 0x38;      // GP0~2 Outputs, GP3~5 Inputs
    //---------------- INIT VARIABLES  --------------------------
    ENdebK = 0;
    NEW_ENCODER_STATE = 0;
    PWMduty = InitialPWMDutyK;       // starting output
    lastENCODER = (GPIO & ENCODER_MASK);  // init the encoder
    //---------------- START TIMER 0 IRQ  -----------------------
    TMR0 = 0;
    INTCON.T0IF = 0;
    INTCON.T0IE = 1;
    INTCON.GIE = 1;
    setBeepPattern(Beep_Long1);
    while(1){
        //--------------------- PROCESS ENCODER  ---------------------
        // Iff NEW state has been posted, process it.
        // ENstate is formatted as 0000llcc where
        //  ll is the last value of the switch
        //  nn is the current value.
        //  The switch runs a gray code (only one bit changes at a time). By
        //  combining the last and current bit inputs, a unique number
        //  0000 to 1111 is created.  This number indicates what happened when
        //  the switch was turned.  For example:
        //  Last value  Current value   State   What happened
        //-------------------------------------------
        //  00          01              1       Rotated CW one click
        //  00          10              2       Rotated CCW one click
        //  and
        //  10          00              8       Rotated CW one click
        //  10          11              11      Rotated CCW one click
        //  also
        //  00          11              3       Illegal, both bits changed,
        //                                      ignored.
        // A total of 4 states indicate CW and 4 indicate CCW.  The other 8
        // possibilities are the 4 possible both bit changes and 4
        // possible no bit changes.  These are ignored.
        // A valid CW or CCW calls increment/decrement which adjusts the PWM.

        if(NEW_ENCODER_STATE){
            NEW_ENCODER_STATE = 0;
            switch (ENstate){
                case 0b00000001:
                case 0b00000111:
                case 0b00001000:
                case 0b00001110:
                    increment();
                    break;

                case 0b00000010:
                case 0b00000100:
                case 0b00001011:
                case 0b00001101:
                    decrement();
                    break;
                // ignore others: bad reading
            }// switch
            /********* Below statements can be used if beeeping is desired
                       During Encoder movement ************************
            if (CurrentDuty != PWMduty) // did duty cycle change?
                setBeepPattern(Beep_Short1);  // yep, one beep   */
        }// if NEW_ENCODER_STATE

        //------------------- GENERATE BEEP PATTERN  --------------------
       // Shifts BeepPattern each time period turning beeper on/off in pattern

        if(BeepTimer_5ms == 0){                       // time to do something?
            if(BeepPattern != 0){                   // more to do?
                if(BeepPattern & BeepCtrlmask)      // yes, beep on/off according bit in pattern
                    soundBEEPER = 1;
                else
                    soundBEEPER = 0;

                BeepTimer_5ms = BeepTimerSet;       // then reload timer
                BeepPattern = BeepPattern >> 1;     // shift it right for next pattern bit
            }
            else
                 soundBEEPER = 0;                   // no bits in pattern, turn it off
        }
     
    }// main while loop
}
By the way..Switch is active LOW
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
@R!f@@ Well, *FLASH*
So I took your code, reassigned my breadboard IO to match your code, compiled it in MikroC, imported the .COF file to MPLAB8.x, ran it with a debug header and it flashed! Wow.
I stepped the code and found that when it wrote 0x38 to GPIO, the port wound up 0x23! (PWM LEDs on!) W.T.F. I looked through the generated assembler and it is correct - movlw 38h movwf GPIO. Zowie.

Uhhhh.... I don't know why that is. There don't seem to be any port inits missing... No analog pins here. Comparators are turned off.

I updated my XC8 code to match your IO map, compiled it and stepped it through the GPIO=0x38 and TRISIO=0x38. As soon as the TRIS hit, my PWM leds turned on! *FLASH* Again, W.T.F. And again, uhhh.....
It does not appear to happen every time. Stepping can aggravate problems like this because the debugger reads the SFRs after each step. Grasping at straws here.

I currently have the GPIO=0x38 line relocated to just before the interrupt is turned on to give TRISIO time to settle after the TRIS?? No more flash but... Don't know.

One thing I did notice is that MikroC is NOT auto-initializing any variables. XC8 initializes everything to 0. PWMbaseK other stuff is not explicitly initialized, relying on the C runtime. I don't usually do that because I invariably get screwed. And I saw that when the PWM LEDs were ON due to the faulty GPIO init, it took writing to GPIO by the initial beep to get control over it and correct the outputs. Maybe your flash is longer because the PWM registers are random on power up.

So try this:
Visit all of the variable declarations and initialize them explicitly.
Separate the TRISIO init from GPIO init by a few instructions.
You also might try using the power up reset timer and / or brown out detector to see if that helps.

Good luck!

EDIT: are you getting the flash when its just the PIC itself (not the debugger). The debugger uses GP0/GP1 -the ICSP lines. The header I'm using is supposed to replace these lines. Maybe it doesn't do it well... Maybe it does but during stepping it uses the port bits and clobbers them.

Do you see the flash when its just a programmed chip without the debugger connected?
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
So I took your code, reassigned my breadboard IO to match your code, compiled it in MikroC, imported the .COF file to MPLAB8.x, ran it with a debug header and it flashed! Wow.
I stepped the code and found that when it wrote 0x38 to GPIO, the port wound up 0x23! (PWM LEDs on!) W.T.F. I looked through the generated assembler and it is correct - movlw 38h movwf GPIO. Zowie.

Uhhhh.... I don't know why that is. There don't seem to be any port inits missing... No analog pins here. Comparators are turned off.

I updated my XC8 code to match your IO map, compiled it and stepped it through the GPIO=0x38 and TRISIO=0x38. As soon as the TRIS hit, my PWM leds turned on! *FLASH* Again, W.T.F. And again, uhhh.....
I went too..like W.T.F was that ????o_O . I did not post it. But here I am with you. :D

It does not appear to happen every time.
Here it is there. Every friggin time.

I currently have the GPIO=0x38 line relocated to just before the interrupt is turned on to give TRISIO time to settle after the TRIS?? No more flash but... Don't know.
I moved them one by one and all ( TRIS, GPIO & GPIOimg) before and after and in middle on Int. Did not help. I gave a 100ms delay between GPIO and TRIS.

Visit all of the variable declarations and initialize them explicitly.
Ummmmm ! you have to break it down here. I am lost some what.

You also might try using the power up reset timer and / or brown out detector to see if that helps.
FLASH IS THERE

are you getting the flash when its just the PIC itself (not the debugger). The debugger uses GP0/GP1 -the ICSP lines. The header I'm using is supposed to replace these lines. Maybe it doesn't do it well... Maybe it does but during stepping it uses the port bits and clobbers them.

Do you see the flash when its just a programmed chip without the debugger connected?
Is the debugger ON in the easyPIC even if I am not using it ?

I have not check the PIC out of the EasyPIC. For that I had to build the entire circuit.
I guess I will do that today and be sure it is not the code that is messing up

I currently have the GPIO=0x38 line relocated to just before the interrupt is turned on to give TRISIO time to settle after the TRIS?? No more flash but... Don't know.
After building the circuit I think I need this part in the code..right ?
Can you show me the part of code where you moved the GPIO.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
I am attaching a zip file of the video. I tried to embed from facebook but did not work. Photobucket embed did not work. It's a 5.6MB video.
It's Video of the PCB I made to test the code out of EasyPIC. The board is exactly what I will use. The encoder is separate and it will be replaced with shaft switch one when it arrived from china
Flash is there and the long buzzer sound too. The Buzzer is replaced with a 5V resonant at 2.4KHz. So the tone is different from that of EasyPIC.
It's official. It's a code issue. It's running the code in post #103.
The two strips on two GP0 and 1.
 

Attachments

Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
I moved the GPIO init as shown:

C:
/************************ MAIN  *********************************************
void main(void) {
    INTCON = 0;         // zap all IRQs
    PIE1 = 0;
    //--------------- CONFIGURE THE SYSTEM  -------------------
    OPTION_REG = OPTIONinit;
    CMCON = CMCONinit;

    //---------------- INIT HARDWARE IO  ----------------------
      
    TRISIO = TRISinit;

    //--------------- INIT VARIABLES  --------------------------
    ENdebK = 0;
    NEW_ENCODER_STATE = 0;
    PWMduty = InitialPWMDutyK;       // starting output
    lastENCODER = (GPIO & ENCODER_MASK);  // init the encoder

    GPIOimg = GPIOinit; // this must agree with the next line
    GPIO = GPIOinit;    // Init the I/O f/fs first.. then TRIS

    //---------------  START TIMER 0 IRQ  -----------------------
    TMR0 = 0;
    T0IF = 0;
    T0IE = 1;
    GIE = 1;
    setBeepPattern(Beep_Alarm);     // howdy!
To initialize your variables you can do something like this when declaring them:
unsigned char PWMbaseK = 0;
or just initialize them in the code itself. Just be sure to init everything, including flags. If there is no specific value in the original code, init it to 0. I looked but did not find a MikroC option to include zeroing the variables and the manual is kind of silent on the subject. You might want to explore that a bit to see if zeroing variables is an option. At any rate, I think we can dispense of the notion that C is a portable language - at least in embedded systems.

I don't know about the EZpic7's debugger. I would blast a chip that runs stand alone and try that.
One piece of good news. I scoped the PWM outputs on power up of a stand alone chip and there is no output, transient or otherwise, on them until I turn the encoder and bring up the brightness. So.. I think its an artifact of the debugger hardware. If you have the same results, we can log it as a 'Known Issue' and proceed. Maybe the cause will become clear. If not, I'll shoot the whole .X project to uCHIP to see what they say.

Carry on!

EDIT: FWIW, here's the .hex file with your scheme. MCLR/ is still enabled so you'll have to pull it up (my debug header requires that be so).
Video??
 

Attachments

Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
The Hex file in #107 has a very tiny flash from the strips but the Led does not flash. I am thinking that it may be due to 10K R I am using at the G to S of the Mosfet. May be the value is high. I will have to decrease the resistance tomorrow and check. I wish someone would confirm this Mosfet part.

The HEX works without MCLR enabled in EasyPIC since I have the pull up enabled to use MCLR as an active low input.

Lemme check with the PCB ckt. BRB.

PS. I like to see the source code of the HEX in #107
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Well ! I am stumped.
Your HEX does not flash in the PCB. It does a tiny flash in the easypic. So I guess ur HEX it is fixed. The PCB is also with 10K gate resistors.

I will compare ur code with mine
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
I am sorry to trouble you but by explicit inits do you mean do I need this
unsigned char PWMbaseK = 0; to all the unsigned char.

I am not getting that part still.

I did a test.
I took your Hex file txt to past it onto a new project. Added only the INTCON. part and changed the void interrupt isr(void) to void interrupt ()
and complied. So nothing else is changed.
Here is that code just in case you wanna see it.

C:
/*
* File:   PWMplay.c
* Author: JohnInTX
* Created on November 4, 2015, 18:23
* XC8 FREE or PRO, Ver 1.35
* This implements a quadrature encoder, PWM output and a blinking LED (heartbeat)
* just for grins.  All timing-sensitive functions are interrupt driven including
* encoder switch debounce, PWM output and derived timers (to do things like
* flash LEDs).
* PIC16F629
*
* Updates:
* Change IO map to match Rifaa's - Explore flash on power up
*
* Replace heartbeat with beeper
* Changed TMR0 reload to +=
* Known issues:
* ENstate not fully initialized on power up
* CONFIG should be visited for final target.
*/

//#include <xc.h>

//********************* PIC CONFIG  ***********************************
// CONFIG
#pragma config FOSC = INTRCIO   // Oscillator Selection bits (INTOSC oscillator: I/O function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-Up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON      // GP3/MCLR pin function select (GP3/MCLR pin function is MCLR)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config CP = OFF         // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)

//********************** PROGRAM CONFIGURATION  *****************************
// Change these as required
#define TMR0period_us  250            // how many usec per TMR0 irq
#define ENdebounce_ms  5              // how many ms to debounce the encoder
#define HeartBeatPeriod_ms 150        // how fast to flash the HB LED

#define PWMbaseN 50                   // how many TMR0 IRQs in one PWM cycle
#define PWMchangePerClick 1           // how much to change duty cycle each encoder click
#define InitialPWMDutyK 0             //PWM on power up (actual counts, not percent)

//********************* IO DEFINITIONS  *******************************
// IO is shadowed i.e. set/clear bits in GPIOimg, NOT on GPIO directly.
// The interrupt will write the port.
// This avoids having to turn off the IRQ when main does IO

// IO PINOUT:
// GP0 Encoder PhaseA input - pulled up with 10K
// GP1 Heartbeat LED output - 1 = LED ON
// GP2 Encoder PhaseB input - pulled up with 10K
// GP3/MCLR - Pulled up with 10K (can disable in CONFIG)
// GP4 Not used in standard program.
// GP5 PWM output: 1 = PWM ON

#define PWMoutmask    0x03      // PWM out is GP0,1
#define Beepermask    0x04      // BEEPER PWM on GP2
//MCLR in for my debugger       // GP3 in
#define ENphaseAmask  0x10      // Phase A is on GP4
#define ENphaseBmask  0x20      // Phase B is on GP5


#define ENCODER_MASK (ENphaseAmask | ENphaseBmask) // bits with encoder inputs
#define TRISinit 0x38  //0b11111000     // Encoder is input, others out
#define GPIOinit 0x38      // Inputs 1, outputs 0
#define CMCONinit 0x7           // Comparators OFF
#define OPTIONinit 0b10001000   // No pullups, TMR0 runs at Tcyc

// *************** CALCULATED CONSTANTS  *******************************

#define TMR0reload 256-TMR0period_us;    // what to reload TMR0 to
#define msec5PSset 5000/TMR0period_us  // count this many IRQs for 5msec
#define ENdebTIKS   ((ENdebounce_ms *1000) / TMR0period_us)
#define HeartBeatTimerSET (HeartBeatPeriod_ms /(5*2)) // flash heartbeat period
#define PWMdutyKinit ((InitialPWMpct * PWMbaseK)/100);

//********************** VARIABLES  **********************************
// Encoder
unsigned char curENCODER,newENCODER,lastENCODER,ENdebK,ENstate,GPIOimg;
bit NEW_ENCODER_STATE;

// PWM
unsigned char PWMbaseK,PWMdutyK,PWMduty;
//bit PWMoutput;

// Derived timers
unsigned char msec5PreScaler;
unsigned char BeepTimer_5ms;     // times beep pattern

// Beeper
bit soundBEEPER;            // 1 = output 50% duty cycle on beep out at 1/2 IRQ rate

// Beeper pattern.  Bit mapped variable gets shifted right each BeepTIK.
// Iff LSbit==1 beeper sounds for one BeepTIK.  If 0, beeper is off for that TIK.
// By scattering bits in BeepPattern, patterns and different beep times can be
// generated.
typedef unsigned int BeepPatternType;
BeepPatternType BeepPattern;
unsigned char CurrentDuty;  // for duty cycle change test (beeps)
#define BeepCtrlmask ((BeepPatternType) 1)   // tests LSbit in char or int or long
#define BeepTIK_ms  50     // ms per tik
#define BeepTimerSet (BeepTIK_ms / 5)

// Beeper patterns for int
#define Beep_OFF 0b0000000000000000     // no beep (copy/paste for new pattern)
#define Beep_Short1 0b0000000000000001  // 1 short beep
#define Beep_Short2 0b0000000000000101  // 2 short beeps
#define Beep_Long1 0b0000000000001111   // 1 long beep
#define Beep_Long2 0b0000111100001111   // 2 long beeps
#define Beep_Alarm 0b0101010101010101   // 8 short beeps
                                        // for you Roger Rabbit fans
#define Beep_Shave_AndA_Haircut 0b1010010110101011

//********************** INTERRUPT SERVICE  **************************
// Interrupts when TMR0 rolls over. Reloads it to trim period then
// processes items below.
// Timer 1 is unused.
// Note that IO is done here.  No direct outputs to GPIO in main is allowed
// unless interrupts are disabled - not super good when running a software
// PWM.  See Heartbeat in main to see how its done.

void interrupt()
{
    //GPIOimg |= ScopeOutmask; // 1= entering IRQ
    //GPIO = GPIOimg;

    TMR0 += TMR0reload;      // reload TMR0 to trim period
    INTCON.T0IF = 0;               // clear IRQ flag

    //------------------ SERVICE PWM  --------------------------------
    // PWMbaseK counts PWMbaseN interrupts to make the PWM period
    // The PWM output will be high for the fraction of this period
    // determined by PWMdutyK.
    GPIOimg &= ~PWMoutmask;     // assume it will be 0
    if(PWMbaseK){
        PWMbaseK--;
        if(PWMdutyK){
           PWMdutyK--;
           GPIOimg |= PWMoutmask;
        }
    }
    else{
        PWMbaseK = PWMbaseN;
        PWMdutyK = PWMduty;
        //GPIOimg |= PWMoutmask;
        }
    GPIO = GPIOimg;         // update port

    //------------------- SERVICE BEEPER  ---------------------------
    if(soundBEEPER)
        GPIOimg ^= Beepermask;       // 50% duty PWM at TMR0 rate
    else
        GPIOimg &= ~Beepermask;    // else, beeper output 0

    GPIO = GPIOimg;                 // update port

    //------------------- SERVICE ENCODER  ---------------------------
    // Reads switch and compares current value to last valid one.
    // Iff its different, it attempts to debounce it by counting
    // each time it enters the interrupt.  When the switch is stable
    // for ENdebTIKS, the previous and current value are combined into
    // one 4bit value which indicates which way the switch turned.
    // See the encoder processor in main for more details.
    // Note that the switch inputs can be on any GPIO input.  This sorts that
    // out.

    curENCODER = (GPIO & ENCODER_MASK);
    if(ENdebK == 0){        // 0 means not debouncing any changes
        if(curENCODER != lastENCODER){  // but it has just changed so..
            ENdebK = ENdebTIKS;
            newENCODER = curENCODER;    // save value to compare
        }
        else;   // else its not changed, do nothing
    } // was not debouncing, debK >0 iff it just started a new one
    else{  // it is currently debouncing
        if(curENCODER != newENCODER){  // changed during debounce!
            ENdebK = ENdebTIKS;         // reset debouncer w/new value
            newENCODER = curENCODER;
        }
        else{   // still same value
            if(--ENdebK == 0){ // dec and test debounce counter iff Z..
                lastENCODER=newENCODER; // update 'last' value for next click
                ENstate = (ENstate << 2);       // shift is to was
                ENstate &= 0b00001100;  // clear out old and unused bits
                                        // then combine new inputs to two bits
                if(newENCODER & ENphaseAmask) ENstate |= 0x01;
                if(newENCODER & ENphaseBmask) ENstate |= 0x02;
                NEW_ENCODER_STATE = 1;
            } // debounce complete-new ENstate
        }// encoder value was same during debounce
    }// its currently debouncing

    //---------------------- SERVICE DERIVED TIMERS  ---------------------------
    // Prescaler counts msec5PSset interrupts to make a 5msec period.
    // From that, as many timers can be created as desired.  Set the timer
    // to the number of IRQ periods to time.  The timer will run to 0 and stop.
    // Poll the timer, when its 0, time is out. Do something, reload and go.
    if(msec5PreScaler == 0){            // iff prescaler ran out..
        msec5PreScaler = msec5PSset;    // reset it and run timer(s)
        if(BeepTimer_5ms) BeepTimer_5ms--;
    }
    else
      msec5PreScaler--;

  //GPIOimg &= ~ScopeOutmask; // 1= entering IRQ
  //GPIO = GPIOimg;
}// IRQ service

//********************* INCREMENT AND DECREMENT  *****************************
// Called by the encoder switch state decoder.  Increments/Decrements PWMduty
void increment(void)
{
    if ((PWMduty + PWMchangePerClick) >= PWMbaseN)
        PWMduty = PWMbaseN;
    else
        PWMduty += PWMchangePerClick;
}

void decrement(void)
{
   if (PWMduty < PWMchangePerClick)
           PWMduty = 0;
   else
       PWMduty -= PWMchangePerClick;
}
//********************* BEEPER CONTROL  *************************************
// Sets pattern, clears timer to force immediate service/terminate current one
void setBeepPattern(BeepPatternType p)
{
    BeepPattern = p;
    BeepTimer_5ms = 0;
}

//************************ MAIN  *********************************************
void main(void) {

    INTCON = 0;         // zap all IRQs
    PIE1 = 0;
    //--------------- CONFIGURE THE SYSTEM  -------------------
    OPTION_REG = OPTIONinit;
    CMCON = CMCONinit;

    //---------------- INIT HARDWARE IO  ----------------------

    TRISIO = TRISinit;

    //--------------- INIT VARIABLES  --------------------------
    ENdebK = 0;
    NEW_ENCODER_STATE = 0;
    PWMduty = InitialPWMDutyK;       // starting output
    lastENCODER = (GPIO & ENCODER_MASK);  // init the encoder

    GPIOimg = GPIOinit; // this must agree with the next line
    GPIO = GPIOinit;    // Init the I/O f/fs first.. then TRIS

    //---------------  START TIMER 0 IRQ  -----------------------
    TMR0 = 0;
    INTCON.T0IF = 0;
    INTCON.T0IE = 1;
    INTCON.GIE = 1;

    setBeepPattern(Beep_Alarm);     // howdy!

    while(1){
        //--------------------- PROCESS ENCODER  ---------------------
        // Iff NEW state has been posted, process it.
        // ENstate is formatted as 0000llcc where
        //  ll is the last value of the switch
        //  nn is the current value.
        //  The switch runs a gray code (only one bit changes at a time). By
        //  combining the last and current bit inputs, a unique number
        //  0000 to 1111 is created.  This number indicates what happened when
        //  the switch was turned.  For example:
        //  Last value  Current value   State   What happened
        //-------------------------------------------
        //  00          01              1       Rotated CW one click
        //  00          10              2       Rotated CCW one click
        //  and
        //  10          00              8       Rotated CW one click
        //  10          11              11      Rotated CCW one click
        //  also
        //  00          11              3       Illegal, both bits changed,
        //                                      ignored.
        // A total of 4 states indicate CW and 4 indicate CCW.  The other 8
        // possibilities are the 4 possible both bit changes and 4
        // possible no bit changes.  These are ignored.
        // A valid CW or CCW calls increment/decrement which adjusts the PWM.

        if(NEW_ENCODER_STATE){
            NEW_ENCODER_STATE = 0;
            CurrentDuty = PWMduty;          // save to check for changes at end
            switch (ENstate){
                case 0b00000001:
                case 0b00000111:
                case 0b00001000:
                case 0b00001110:
                    increment();
                    break;

                case 0b00000010:
                case 0b00000100:
                case 0b00001011:
                case 0b00001101:
                    decrement();
                    break;
                // ignore others: bad reading
            }// switch
            if (CurrentDuty != PWMduty) // did duty cycle change?
                setBeepPattern(Beep_Short1);  // yep, one beep
        }// if NEW_ENCODER_STATE

        //------------------- GENERATE BEEP PATTERN  --------------------
       // Shifts BeepPattern each time period turning beeper on/off in pattern

        if(BeepTimer_5ms==0){                       // time to do something?
            if(BeepPattern != 0){                   // more to do?
                if(BeepPattern & BeepCtrlmask)      // yes, beep on/off according bit in pattern
                    soundBEEPER = 1;
                else
                    soundBEEPER = 0;

                BeepTimer_5ms = BeepTimerSet;       // then reload timer
                BeepPattern = BeepPattern >> 1;     // shift it right for next pattern bit
            }
            else
                 soundBEEPER = 0;                   // no bits in pattern, turn it off
        }

    }// main while loop
}
And attached are the two Hex. Ur's and mine

I tried one by one with EasyPIC.
Mine flashed. Your one does not (teenee flash)
So tried three times each hex and the same result.

All other functions remain the same.

Is the MikroC compiler doing this ?

{ed}
PS the Mosfet G to D was a typo..Sorry.
 

Attachments

JohnInTX

Joined Jun 26, 2012
4,787
I am sorry to trouble you but by explicit inits do you mean do I need this
unsigned char PWMbaseK = 0; to all the unsigned char.
Yes. Anything that is not already explicitly initialized should be declared like you show OR add code in the init section that does the same thing. Bit flags and the beep timer included.

<xc.h> is the master include file that XC8 uses that has all of the register defines for the particular PIC in use. Comment it out for MikroC - it determines the includes from checking boxes when you create the project.

I ran up your .HEX and it indeed flashes my LEDs here (no mosfets). So its a real thing. At this point I have to believe that you can get it better by clearing all of the variables that MikroC is not doing by default.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
Hey..good news :D
It's fixed.
Only one line added just now.

C:
void main() {                 //
    INTCON = 0;         // Clear all IRQs.
    PIE1 = 0;           // Clear all Interrupts.
    OPTION_REG = 0x88;  // No Pullups, TMR0 runs at Tcyc.
    CMCON = 0x7;        // Comparators off.
    //---------------- INIT HARDWARE IO  ----------------------
    GPIOimg = 0x38;     // this must agree with the next line
    GPIO = 0x38;        // Init the I/O f/fs first.. then TRIS
    TRISIO = 0x38;      // GP0~2 Outputs, GP3~5 Inputs
    //---------------- INIT VARIABLES  --------------------------
    ENdebK = 0;
    NEW_ENCODER_STATE = 0;
    PWMduty = InitialPWMDutyK;       // starting output
    lastENCODER = (GPIO & ENCODER_MASK);  // init the encoder
    PWMbaseK = 0;
    //---------------- START TIMER 0 IRQ  -----------------------
    TMR0 = 0;
    INTCON.T0IF = 0;
    INTCON.T0IE = 1;
    INTCON.GIE = 1;
    setBeepPattern(Beep_Short2);
PWMbaseK = o;
 

JohnInTX

Joined Jun 26, 2012
4,787
Cool. FWIW, here's the init code generated by XC8 that is missing in MikroC. It automatically does what MikroC does not, leaving you with a random system on power up. Comments added by me. Note that it does not init BeepCtrlPattern. Apparently it 'knows' that the variable is never used before its written to by the code.. Nice. Anyway, your C code should do all of this explicitly (in C of course).
Code:
Line  Address  Opcode   Label                  Disassembly         

     1   000     280B __ftpack    GOTO 0xb  ; starts HERE on RESET, jumps to 000Bh                        
     2   001     3FFF                                               
     3   002     3FFF                                               
     4   003     3FFF                                               
     5   004     00DE             MOVWF 0x5e ; interrupt vector - context save then jump to isr at 0019h                       
     6   005     0E03             SWAPF STATUS, W                   
     7   006     1283             BCF STATUS, 0x5                   
     8   007     00AE             MOVWF 0x2e                        
     9   008     080A             MOVF PCLATH, W                    
    10   009     00AF             MOVWF 0x2f                        
    11   00A     2819             GOTO isr
                         
    12   00B     280C             GOTO 0xc  ; bad optimizer... goes to next line                        
    13   00C     01B2             CLRF 0x32 ; Here its zeroing out all of the variables automatically - generated by the compiler. MikroC does not do this!                       
    14   00D     01A0             CLRF BeepTimer_5ms                
    15   00E     01A1             CLRF CurrentDuty                  
    16   00F     01A2             CLRF ENdebK                       
    17   010     01A3             CLRF ENstate                      
    18   011     01A4             CLRF PWMbaseK                     
    19   012     01A5             CLRF PWMduty                      
    20   013     01A6             CLRF PWMdutyK                     
    21   014     01A7             CLRF curENCODER                   
    22   015     01A8             CLRF msec5PreScaler               
    23   016     01A9             CLRF newENCODER                   
    24   017     0183             CLRF STATUS   ; selects RAM bank 0                    
    25   018     286C             GOTO main   ; after zeroing, goes to the first 'C' line.. 
                  
    26   019     3006 isr         MOVLW 0x6                         
    27   01A     0781             ADDWF TMR0, F 
    ;... and so on..
 

Thread Starter

R!f@@

Joined Apr 2, 2009
9,918
CSS:
    PWMduty = InitialPWMDutyK;       // starting output
    lastENCODER = (GPIO & ENCODER_MASK);  // init the encoder
    NEW_ENCODER_STATE = 0;
    PWMbaseK = 0;
    ENdebK = 0;
    BeepTimer_5ms = 0;
// CurrentDuty = 0;
    ENstate = 0;
    PWMduty = 0;
    curENCODER = 0;
    msec5PreScaler = 0;
    newENCODER = 0;
More good news..You are right about the compiler. You should inform uChip ;)
I added the above. ( I am not using the encoder beeping)
Now all good. Even the buzzer is giving neat good 2 beeps

So shall we move on. :p.

By the way..what is simplest way to increase the pitch of the 2.4Khz buzzer I have in the ckt. The easyPic one 3.8Khz and it's tone is nice, but the one in my ckt is lower.
I know you know what to change to increase the freq. but I am checking if I can change it.:D
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Hey..is it possible to use the same sound timertik to debounce the switch ?
You are reading my mind.
First though, I would adjust the TMR0 interrupt time to give you the sound frequency you want. Two interrupts per cycle; 250us is a period of 500us for a frequency of 2000Hz. 3800 Hz is doable with a higher interrupt rate 131usec (131Tcyc at 4MHz) or whatever you decide to use. The PWM and beep flasher will follow along - within reason.

TMR0period_us is defined as 250 in PROGRAM CONFIGURATION. Change it as described to change the beep freq.
All of the other constants like the 5msec timer prescaler are calculated off of this value so they should re-calculate accordingly without any changes. These are in CALCULATED CONSTANTS in the code.

Recall that the limiting factor here is how fast we can interrupt before the PIC is not fast enough to execute all of the interrupt code before the next interrupt occurs. But, I posted a couple of scope shots earlier that showed that we were spending only ~15% of our total CPU time in the interrupt routine. We've added a little (the beep PWM) but I am pretty sure we can increase the IRQ rate to get you to 3800Hz with plenty left over to process main(). Plus the way main is written, it can get interrupted many times per 'while' loop.

So, pick a beep freq then we'll know interrupt rate that will do the switch sampling for debounce.

Nice?

EDIT: The button will work in two stages. First, its debounced in the interrupt routine. The debouncer will emit information about the button. At the very least a flag indicating the current debounced button. I usually call that one something like BUTTON_HELD. The second and more important output is a latching flag that gets set once each time the button is pressed and debounced - BUTTON_REQUEST. So now we have another black box for main to use.

We'll add another service to main() that processes these two flags. If we want to time the button push, we might use BUTTON_HELD. If we want to count presses or toggle something on each push, we use BUTTON_REQUEST. Main counts/toggles when the flag is set and then clears the flag. BUTTON_REQUEST will be set again only when the switch is released then fully debounced closed again. One push - one flag. This is a classic 'producer-consumer' approach to coding. The IRQ does its magic and 'produces' the REQUEST flag and HELD status. Main 'consumes' the REQUEST by clearing the flag and doing something as a result. Main inspects the switch status by sampling the HELD flag. Note that both flags are valid continuously - main never has to stop-down and wait on some delay-oriented debouncer. Both Joey999 and MMclaren suggested similar approaches as well. One or both of them also suggested a state-machine to do the logic. I would do that as well. BUT we are getting ahead of ourselves...

So:
Frequency setting first.
Button debounce and flag outputs by IRQ next.
Main routine to use the button output. Maybe a few flags but a state-machine is possible. Easy!
 
Last edited:
Top