Please help my son! 16F628A timer circuit/program

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
My 12 year old son is building a circuit to activate/deactivate a KNEX motor based on a timing circuit (on for ~55 seconds). I am trying to help him get a jump start in understanding how to do it using a 16F628A programming in C

We have been googling a ton and trying to understand how it all works (with a 1 week deadline). We are unable to understand how to use the config settings and the code as written doesn't even turn on the LED yet! The code below is our attempt to turn on LED based on switch input.

The end goal is a program that when powered up turns on LED1 and looks for input from SW1. Upon input from SW1 it should turn on LED2 and delay for 3 seconds while watching SW2 (for cancel). If no input from SW2 in 3 seconds then turn on the Relay for 55 seconds while also activating LED2 to indicate the program is running. After 55 seconds expires go back to watching SW1 with LED1 active.

Currently the test circuit has:

MCLR and VDD tied together with an 8K ohm resistor
VDD tied to 5V input
VSS tied to ground
RB0 to switch to ground
RB1 to switch to ground
RB2 to 330ohm resistor to LED to ground
RB3 to 330ohm resistor to LED to ground

Please help!

Rich (BB code):
/************************
* Description: LED Test with buttons
* Turn on LED for x seconds
* Pin Assignments:
*   RB0 = Switch 1  
*    RB1 = Switch 2
*    RB2 = LED1
*    RB3 = LED2
*    RB4 = Relay connection
 **************************/


#define _XTAL_Frec 4000000
#include<xc.h>




#define LED_ON      1       // LED is connected cathode to ground
#define LED_OFF     0

#define RELAY_ON    1       //RELAY is connected to ground
#define RELAY_OFF   0       //RELAY not connect to ground

#define BUTTON_ON   0       // Button input is low when button pressed
#define BUTTON_OFF  1       // (this is how a pull-up resistor button works)

#define I_O_OUT     0       // standard TRIS definitions
#define I_O_IN      1       // zero is 0ut, one is 1n

#define SW_A        RB0     // switch A is on PORTB.0
#define SW_B        RB1
#define SW_A_TRIS   TRISB0  // switch B is on PORTB.1
#define SW_B_TRIS   TRISB1

#define LED_A       RB2     // LED A is on PORTB.2
#define LED_B       RB3     // LED B is on PORTB.3
#define RELAY       RB4     // RELAY is connected to ground
#define LED_A_TRIS  TRISB2
#define LED_B_TRIS  TRISB3
#define RELAY _TRIS TRISB4

void main(void)
{
  SW_A_TRIS  = I_O_IN;    // set up I/O pin directions
  SW_B_TRIS  = I_O_IN;
  LED_A_TRIS = I_O_OUT;
  LED_B_TRIS = I_O_OUT;

  // now loop forever to sense buttons
  while(1)
  {
    if (SW_A == BUTTON_ON) // test the first switch
        LED_A = LED_ON;
    else
      LED_A = LED_OFF;

    if (SW_B == BUTTON_ON) // test the second switch
      LED_B = LED_OFF;
    else
      LED_B = LED_ON;

  }
}
 

thatoneguy

Joined Feb 19, 2009
6,359
My son picked up on PICAXE at a younger age, the capabilities of which are well within your parameters. They are programmed in BASIC and run fairly fast.

Which C compiler are you using? Are you programming with a PICKit 2/3?

For your program, do you have a 10k Resistor pulling the switch High when it isn't pressed? The PIC will only see 0V or floating (usually read as zero) if there isn't a pullup resistor. Report back if this doesn't solve the problem.

That aside, unrelated to your exact problem, but you may run into it in the future: On ALL PICs that have ADC and comparators, they are ON at power up. You need to disable both the ADC and comparators, otherwise the analog ports will all be Inputs, even if TRIS is set correctly.

Add the following lines to use PortA for digital I/O:
ADCON1=0x86;
CMCON=0x07;
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
My son picked up on PICAXE at a younger age, the capabilities of which are well within your parameters. They are programmed in BASIC and run fairly fast.

Which C compiler are you using? Are you programming with a PICKit 2/3?

For your program, do you have a 10k Resistor pulling the switch High when it isn't pressed? The PIC will only see 0V or floating (usually read as zero) if there isn't a pullup resistor. Report back if this doesn't solve the problem.

That aside, unrelated to your exact problem, but you may run into it in the future: On ALL PICs that have ADC and comparators, they are ON at power up. You need to disable both the ADC and comparators, otherwise the analog ports will all be Inputs, even if TRIS is set correctly.

Add the following lines to use PortA for digital I/O:
ADCON1=0x86;
CMCON=0x07;
We will add the two lines above. Unfortunately I am not very informed on this stuff and its the blind leading the blind. Hopefully his young age will allow him a better starting point!

We are using MPLAB X, XC8 compiler and the Pickit 3 for programming.

The 10K resistor was not part of his circuit and will be added as well for both switches.

Gee you have a very readable coding style. <grin>
If I "borrowed" your code for the basis of this I thank you....it is readable even for my novice eyes and very helpful...hoping we can figure out what else we are missing!
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
I tried adding ADCON1=0x86; but the code window indicates this is a problem (word is red underline). I added the resistors to the switch ports. Nothing happens when I apply power to the circuit at this point but I haven't reprogrammed the chip as we need to solve the ADCON1 issue.

I tried to understand from the 16F628A datasheet how to set the __config line but I don't understand the options. Which do I need to enable/disable to allow a simple timer and LED lighting?
 

thatoneguy

Joined Feb 19, 2009
6,359
You'll want to read the Datasheet for the 16F628A Closely. It is long, but it holds a TON if information.

The config should disable MCLR and enable the Internal Oscillator, usually LVP (Low Voltage Programming) is disabled as well. You'll want to look at the sample files that come with the compiler for a line you can copy that has those settings in it, or find in the help system what the exact keywords are.

The ADCON1 and CMCON only change PortA, since your I/O and LEDs are on PortB, it won't fix the issue. After looking at the datasheet, the 628A doesn't have ADC, so you can leave that line out, just disable the comparators.

If you didn't have pullups on the switches, or the internal oscillator running, that would explain why the test circuit didn't work.
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
You'll want to read the Datasheet for the 16F628A Closely. It is long, but it holds a TON if information.

The config should disable MCLR and enable the Internal Oscillator, usually LVP (Low Voltage Programming) is disabled as well. You'll want to look at the sample files that come with the compiler for a line you can copy that has those settings in it, or find in the help system what the exact keywords are.

The ADCON1 and CMCON only change PortA, since your I/O and LEDs are on PortB, it won't fix the issue. After looking at the datasheet, the 628A doesn't have ADC, so you can leave that line out, just disable the comparators.

If you didn't have pullups on the switches, or the internal oscillator running, that would explain why the test circuit didn't work.

Adding:

Rich (BB code):
#pragma config MCLRE = OFF, WDTE = OFF, CP = OFF, LVP = OFF, FOSC = INTOSCIO
Did the trick. I wish I understood what it all meant...I get the Code Protection and Watchdog(kinda) and LVP part but I am not firm on the MCLRE and FOSC parts...especially the FOSC as I know I need that later for the delay routine.

Now that the code works, what is the best(or easiest) way to delay for 55 seconds without losing too much time? My son is starting to get excited seeing the LEDs light up!
 

thatoneguy

Joined Feb 19, 2009
6,359
There isn't a function for delay in the library functions. Generally, delays are created by running higher priority code. However, for your application, it seems there is a macro, found in the manual:

In-line delay values The delay value specified with the in-line delay function (_delay) is limited
to 179,200 on PIC18 devices. With PIC10/12/16 devices, this value may be up to
50,659,000.
In-line delays On PIC10/12/16 targets, the only in-line delay function implemented is _delay.
This uses NOP instructions, when required, as part of the delay sequence. There is currently
no equivalent version of this function that uses the CLRWDT instruction. The function, _delaywdt
which is available for PIC18 targets, does use the CLRWDT instruction.
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
There isn't a function for delay in the library functions. Generally, delays are created by running higher priority code. However, for your application, it seems there is a macro, found in the manual:
I am unsure how to execute a macro, unfortunately the reading I do doesn't make me feel smarter...

This is what we have built for a delay. It allows us to pass in a time as an int (seconds * 2) and also has some options for getting out of the loop via button_b. The looptuner is used to try to get close to exact timing.

We haven't yet included the relay switching portion but that is next.

Rich (BB code):
void delay() {
    int bigcounter = 0;
    int counter = 0;
    int looptuner = 19500;
    for(bigcounter = 0; bigcounter < 6; bigcounter++)  //set total calue of bigcounter in seconds * 2 - This is our cancel period
    {
        for(counter = 0; counter < looptuner; counter++)     //approx .5 second delay
            {
              if (SW_B == BUTTON_ON) break;                 //if switch b is press we exit
            }
            //LED_B = ~LED_B;
            if (SW_B == BUTTON_ON) break;                   //if switch b is press we exit
            ;
        for(bigcounter = 0; bigcounter < 30; bigcounter++)  //set total calue of bigcounter in seconds * 2
        {
            for(counter = 0; counter < looptuner; counter++)     //approx .5 second delay
            {
              if (SW_B == BUTTON_ON) break;                 //if switch b is press we exit
            }
            LED_B = ~LED_B;
            if (SW_B == BUTTON_ON) break;                   //if switch b is press we exit
            ;
        }
    }
}
 

thatoneguy

Joined Feb 19, 2009
6,359
For a 55 second time delay, I'd suggest interrupts, which will require you to read both the datasheet, the compiler help file, and there are MANY useful App Notes available from Microchip.

As I stated earlier, using a delay() function simply wastes power.

For an interrupt, you do the math to get the right prescaler for the clock you are using, then, when that clock counts down to zero, the interrupt() function (which you'll have to write) is called, which resets the counter, updates flags, and returns to program.


Interrupts are a programmer's best friend, once you learn the usage.

That way, with interrupts, the keypad scanning and display updating is done in real time. PICs run millions of operations per second, while humans can perceive about 100 visual changes per second, and input about 8 characters per second (both on the optimal case). The display update is extremely slow relative to humans, so having the interrupt check for timer, and the main loop checking input and do math, you end up with no "keyboard lag" which takes place when you are trying to change the display parameters at the same time the green light delay is being called.
 

takao21203

Joined Apr 28, 2012
3,702
You don't have to use full interrupt functions (real interrupt).

You can simply poll the timer interrupt flag which gets set anyway.

I use this sometimes for serial port (USART), because the response is faster and it only requires one line.

The 16F628 is comfortable it also has a 16bit timer (TIMER1).

I don't really understand why people use these macros.

Rich (BB code):
while(!TMR1IF);
is all what's needed.

And you need to reset each time:
Rich (BB code):
TMR1IF=0;
The TIMER1 works well in the MPLAB simulator no need to reflash each time.
 

MMcLaren

Joined Feb 14, 2010
853
The XC manual does show a __delay_ms() function;

3.5.10 How Can I Implement a Delay in My Code?
If an accurate delay is required, or if there are other tasks that can be performed during
the delay, then using a timer to generate an interrupt is the best way to proceed.
If these are not issues in your code, then you can use the compiler’s in-built delay
pseudo-functions: _delay, __delay_ms or __delay_us, see Appendix A. “Library
Functions”. These all expand into in-line assembly instructions or a (nested) loop of
instructions which will consume the specified number of cycles or time. The delay argument
must be a constant and less than approximately 179,200 for PIC18 devices and
approximately 50,659,000 for other devices.
Note that these code sequences will only use the NOP instruction and/or instructions
which form a loop. The alternate PIC18-only versions of these pseudo-functions, e.g.,
_delaywdt, may use the CLRWDT instruction as well. See also Appendix A. “Library
Functions”.
 

t06afre

Joined May 11, 2009
5,934
You can use the __delay_ms(x) function in a loop. For the cancel function (SW2). You can say run a 25 msec loop 120 times and check if the SW2 is pressed. If pressed you simply using the break statement to exit from the loop. No need to over complicate things here. And it will be accurate enough. I guess if you loose a few 1/100 sec (in total) in this loop it will not count much
Adding:My son is starting to get excited seeing the LEDs light up!
How about the dad, I guess he was also some excited:D
 

takao21203

Joined Apr 28, 2012
3,702
The usual solution is a 32 KHz crystal together with the TIMER1 or a RTC chip.

It is also possible to derive it from 4 MHz, 8 MHz or the like, but that's more complicated.

Normally if you want a 55 minutes timer, you want 55 minutes, and not 44 minutes and 50 seconds.

You can use a calculator to see how many seconds that makes, but for simplicity sake, a seconds and minutes mechanism produces better code.

Timers and stopwatches only become really interesting when they have a display. Then you can't use delay anyway.

If the timer is working repeatedly day after day, and you don't use an exact timebase, it will drift slowly through the 24 hours schedule.

The TIMER1 is not that difficult to use or to program.
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
For a 55 second time delay, I'd suggest interrupts, which will require you to read both the datasheet, the compiler help file, and there are MANY useful App Notes available from Microchip.

As I stated earlier, using a delay() function simply wastes power.

For an interrupt, you do the math to get the right prescaler for the clock you are using, then, when that clock counts down to zero, the interrupt() function (which you'll have to write) is called, which resets the counter, updates flags, and returns to program.


Interrupts are a programmer's best friend, once you learn the usage.

That way, with interrupts, the keypad scanning and display updating is done in real time. PICs run millions of operations per second, while humans can perceive about 100 visual changes per second, and input about 8 characters per second (both on the optimal case). The display update is extremely slow relative to humans, so having the interrupt check for timer, and the main loop checking input and do math, you end up with no "keyboard lag" which takes place when you are trying to change the display parameters at the same time the green light delay is being called.
Thanks for the information about interrupts. I knew from my reading that my method was not efficient. At this point we needed something that worked which we have thanks to all the help received thus far. Next we will try to understand how to change the process to be more reliable and precise by using the interrupts. For non-technical people (and 12 year olds with ADD) the datasheets can be quite daunting!

My son is actually excited about learning programming (not just microchips) so this is a perfect project for him to understand that there are multiple ways to do things and some have distinct advantages over others. This means he needs to look for the best, not just the first, answer.

Task list:
Add relay to circuit and make sure it functions as intended
find out what a prescaler is and how to compute it
find out how an interrupt is incorporated and write them as needed
 

Thread Starter

Tigthwad

Joined Oct 27, 2009
31
You don't have to use full interrupt functions (real interrupt).

You can simply poll the timer interrupt flag which gets set anyway.

I use this sometimes for serial port (USART), because the response is faster and it only requires one line.

The 16F628 is comfortable it also has a 16bit timer (TIMER1).

I don't really understand why people use these macros.

Rich (BB code):
while(!TMR1IF);
is all what's needed.

And you need to reset each time:
Rich (BB code):
TMR1IF=0;
The TIMER1 works well in the MPLAB simulator no need to reflash each time.
I need to do more reading as I don't understand how to work with TIMER1. The code lines you posted look simple enough but I would need to see them in context to understand what they are doing I think.


The XC manual does show a __delay_ms() function;
I saw this multiple places but when I include this in the code it showed an error...I must not be including something required for it to work. It would be simpler than our loop that is based on just a counter for sure.

You can use the __delay_ms(x) function in a loop. For the cancel function (SW2). You can say run a 25 msec loop 120 times and check if the SW2 is pressed. If pressed you simply using the break statement to exit from the loop. No need to over complicate things here. And it will be accurate enough. I guess if you loose a few 1/100 sec (in total) in this loop it will not count much

How about the dad, I guess he was also some excited:D
If I can figure out how to make the __delay_ms(x) work in the code I will upgrade to that but for now the solution eludes me.

I was excited mostly because I have been pulling my hair out and feeling like a moron for not being able to complete a simple "Hello World" type of program.

The usual solution is a 32 KHz crystal together with the TIMER1 or a RTC chip.

It is also possible to derive it from 4 MHz, 8 MHz or the like, but that's more complicated.

Normally if you want a 55 minutes timer, you want 55 minutes, and not 44 minutes and 50 seconds.

You can use a calculator to see how many seconds that makes, but for simplicity sake, a seconds and minutes mechanism produces better code.

Timers and stopwatches only become really interesting when they have a display. Then you can't use delay anyway.

If the timer is working repeatedly day after day, and you don't use an exact timebase, it will drift slowly through the 24 hours schedule.

The TIMER1 is not that difficult to use or to program.
Is a 32mhz crystal easier to work with than the included 4mhz in the chip? I have a 32 mhz crystal we could use if needed.

In our case the timer is for 55 seconds only....just turning on a motor circuit and then turning it off again, all on demand so no scheduled off time required. A display would be cool but much more complex for sure.
 

thatoneguy

Joined Feb 19, 2009
6,359
Is a 32mhz crystal easier to work with than the included 4mhz in the chip? I have a 32 mhz crystal we could use if needed.

In our case the timer is for 55 seconds only....just turning on a motor circuit and then turning it off again, all on demand so no scheduled off time required. A display would be cool but much more complex for sure.
Crystals are more accurate than the internal oscillator, it depends if you want a delay of exactly 55.00 seconds (crystal) or somewhere between 54 and 56 seconds (Internal Oscillator). 4Mhz or 20Mhz crystals or oscillators require 3 parts (crystal) or one 4 pin part (oscillator) more on the PCB, close to the PIC. The faster Oscillators, 4M/20M, will give you a more accurate 55 seconds, since a 5% error at 4Mhz reduces a bit after prescaling, while the drift of a 32.768 kHz clock can be extreme if the temperature changes, resulting in RC Oscillator type timing accuracy.

For what you are doing, it sounds like the internal Oscillator will be fine for the effect lite.
 

MMcLaren

Joined Feb 14, 2010
853
The end goal is a program that when powered up turns on LED1 and looks for input from SW1. Upon input from SW1 it should turn on LED2 and delay for 3 seconds while watching SW2 (for cancel). If no input from SW2 in 3 seconds then turn on the Relay for 55 seconds while also activating LED2 to indicate the program is running. After 55 seconds expires go back to watching SW1 with LED1 active.
When does LED1 get turned off?

After pressing SW1 and turning on LED2 and waiting for 3 seconds, why would you need to turn on LED2 again when you activate the relay? Is this a typo?
 

MMcLaren

Joined Feb 14, 2010
853
MMcLaren said:
The XC manual does show a __delay_ms() function;
I saw this multiple places but when I include this in the code it showed an error...I must not be including something required for it to work. It would be simpler than our loop that is based on just a counter for sure.
It works in my MPLAB 8.84 + XC8 environment...




Rich (BB code):
/********************************************************************
 *                                                                  *
 *  Project: 12F1822 Delay Demo                                     *
 *   Source: 12F1822 Delay Demo.c                                   *
 *   Author: Mike McLaren, K8LH                                     *
 *  (C)2013: Micro Application Consultants, All Rights Reserved     *
 *     Date: 06-Feb-13                                              *
 *                                                                  *
 *  12F1822 Delay Demo                                              *
 *                                                                  *
 *                                                                  *
 *      IDE: MPLAB 8.84 (tabs = 4)                                  *
 *     Lang: Microchip XC8 v1.11                                    *
 *                                                                  *
 ********************************************************************/

   #include <xc.h>

   __CONFIG(FOSC_INTOSC & WDTE_OFF & MCLRE_OFF);
   __CONFIG(LVP_OFF & PLLEN_OFF);

   #define _XTAL_FREQ 8000000

  /******************************************************************
   *  function prototypes                                           *
   ******************************************************************/
  /******************************************************************
   *  type definitions                                              *
   ******************************************************************/
  /******************************************************************
   *  variables and constants                                       *
   ******************************************************************/
  /******************************************************************
   *  low level drivers                                             *
   ******************************************************************/
  /******************************************************************
   *  functions                                                     *
   ******************************************************************/
  /******************************************************************
   *  main init                                                     *
   ******************************************************************/

   void main()
   { ANSELA = 0;                // make pins digital
     TRISA = 1<<RA1;            // RA1 input, all others output
     PORTA = 0;                 // all output latches low
     OSCCON = 0b01110010;       // initialize 8-MHz INTOSC
     while(!HFIOFS);            // wait until OSC stable

  /******************************************************************
   *  main loop                                                     *
   ******************************************************************/

     while(1)                   //
     { __delay_ms(200);         //
       PORTA ^= 0b00000001;     // toggle RA0 output
     }                          //
   }
 

Attachments

Last edited:

t06afre

Joined May 11, 2009
5,934
Try this, it is something that your son can understand and maybe improve also ;) The other suggestions are indeed correct. But way over anything what is expected from a 12 year old boy. This may perhaps be cheating. But as I see it. If your son understand this. He has also learned something. Remember this is your sons science fair, not yours ;)
Rich (BB code):
/************************
* Description: LED Test with buttons
* Turn on LED for x seconds
* Pin Assignments:
*   RB0 = Switch 1
*    RB1 = Switch 2
*    RB2 = LED1
*    RB3 = LED2
*    RB4 = Relay connection
 **************************/
#define _XTAL_FREQ 4000000
#include<xc.h>
#define LED_ON      1       // LED is connected cathode to ground
#define LED_OFF     0
#define RELAY_ON    1       //RELAY is connected to ground
#define RELAY_OFF   0       //RELAY not connect to ground
#define BUTTON_ON   0       // Button input is low when button pressed
#define BUTTON_OFF  1       // (this is how a pull-up resistor button works)
#define I_O_OUT     0       // standard TRIS definitions
#define I_O_IN      1       // zero is 0ut, one is 1n
#define SW_A        RB0     // switch A is on PORTB.0
#define SW_B        RB1
#define SW_A_TRIS   TRISB0  // switch B is on PORTB.1
#define SW_B_TRIS   TRISB1
#define LED_A       RB2     // LED A is on PORTB.2
#define LED_B       RB3     // LED B is on PORTB.3
#define RELAY       RB4     // RELAY is connected to ground
#define LED_A_TRIS  TRISB2
#define LED_B_TRIS  TRISB3
#define RELAY TRISB4
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & BOREN_OFF & LVP_OFF & CPD_OFF & CP_OFF);
unsigned char i;
unsigned char flag;
void main(void)
{
  CMCON=0x07;
  flag=0;
  SW_A_TRIS  = I_O_IN;    // set up I/O pin directions
  SW_B_TRIS  = I_O_IN;
  LED_A_TRIS = I_O_OUT;
  LED_B_TRIS = I_O_OUT;
  LED_A=LED_ON;
  LED_B=LED_OFF;
OSCF=1;
/*bit 3 OSCF: INTOSC Oscillator Frequency bit
1 = 4 MHz typical
0 = 48 kHz typical
*/
  // now loop forever to sense buttons
  while(1)
  {
      while(SW_A==1);
      LED_B=LED_ON;
      for(i=0;i<40;i++)
        {
          if (SW_B==0)
          {
              LED_B=LED_OFF;
              flag=1;
              break;
          }
          __delay_ms(75);
          LED_B=!LED_B;
         }
       if(flag==0)
       {
          LED_B=LED_ON;
          RELAY=RELAY_ON;
          for(i=0;i<55;i++)
          {
              __delay_ms(1000);
          }
          RELAY=RELAY_OFF;            
       } 
       LED_B=0; 
       flag=0;
  }
}//end main
 

Attachments

Top