1
\$\begingroup\$

I am completely new to assembly, and I must develop a counter using PIC16F628A, a push button, and a display. Additionally it there will be an external oscillator (555).

I made some progress on this, but I think I need some help from you people. At first I did a delay based on decrements in order to be able to watch numbers on the display.

My problem now is that once I press the button, I need it to count only one number, independently how much time I keep it pressed. Something like, if it changes state up, it will increment 1. I believe this must be done with interrupts, I guess.

Now, what is the best solution to my problem? External interrupt, by state interrupt? Any other thing?

\$\endgroup\$
  • \$\begingroup\$ How is the switch wired to the pin? Is it so that you get a 1 when pressed? Or a 0 when pressed? I also gather you are not allowed to use a timer for this. Is that correct? \$\endgroup\$ – jonk Jul 2 '17 at 16:15
  • \$\begingroup\$ A zero, actually it is demanded to be used pull up buttons. \$\endgroup\$ – ERS Jul 2 '17 at 16:37
  • \$\begingroup\$ If you can test for the button being pressed, you can also test for when the button has been released. \$\endgroup\$ – gbarry Jul 2 '17 at 16:55
  • \$\begingroup\$ The problem is not testing it being or not being pressed. The problem is doing it on a way that you press the button for one minute, a number is incremented on the display, one and only one number. The way I was doing this, while I kept the button pressed, the display was adding numbers. \$\endgroup\$ – ERS Jul 2 '17 at 17:03
  • \$\begingroup\$ No reason it has to be an interrupt. Why can't the program check that the button is pressed now and that it was unpressed last time the program checked the button? \$\endgroup\$ – immibis Jul 3 '17 at 5:55
1
\$\begingroup\$

Since you cannot use a timer (gathered from comments you've made), you need a suitable delay routine to provide a specific period of time. I like the period of \$8\:\textrm{ms}\$, from prior experience. But you can use any period you feel is appropriate. Assuming that your processor is using the factory calibrated \$4\:\textrm{MHz}\$ rate, the instruction cycle will be \$1\:\mu\textrm{s}\$ and it will take \$8,000\$ cycles to make up an \$8\:\textrm{ms}\$ period.

The delay code should probably be made into a subroutine, to avoid having to replicate it over and over.

DELAY8MS    MOVLW   0x3E
            MOVWF   DLO
            MOVLW   0x07
            MOVWF   DHI
            DECFSZ  DLO, F
            GOTO    $+2
            DECFSZ  DHI, F
            GOTO    $-3
            NOP
            GOTO    $+1
            RETURN

The total time occupied by the above routine can be computed as:

$$t=5\cdot\left[D_{LO}+2+256\cdot\left(D_{HI}-1\right)\right]$$

where \$1 \le D_{LO}\le 256\$ and \$1 \le D_{HI}\le 256\$, with 0 interpreted as 256. The CALL and RETURN instructions take up 2 cycles each and the above code takes all of that into account. Calling it should take exactly \$8,000\$ cycles and, at \$4\:\textrm{MHz}\$ this means \$8\:\textrm{ms}\$.

You will have to create those two variables, \$D_{LO}\$ and \$D_{HI}\$ somewhere. That can be done like this, I think:

            CBLOCK
DLO
DHI
            ENDC

There are, of course, other ways. And you can add an absolute address to the CBLOCK line if you want to place the block somewhere specific.

Now that you have a delay routine, you can proceed to the next step. You need two new routines. One that repeatedly delays until the button becomes active and one that repeatedly delays until the button becomes inactive. Debouncing is included here:

ACTIVE      CALL    DELAY8MS
            BTFSC   PORTx, PINy
            GOTO    ACTIVE
            CALL    DELAY8MS
            BTFSC   PORTx, PINy
            GOTO    ACTIVE
            RETURN

INACTIVE    CALL    DELAY8MS
            BTFSS   PORTx, PINy
            GOTO    INACTIVE
            CALL    DELAY8MS
            BTFSS   PORTx, PINy
            GOTO    INACTIVE
            RETURN

I don't know your port or pin number, so I just put in "dummy" values there. You need to replace them, properly. The above two routines assume that 0 is active and 1 is inactive.

Now you can write your main code:

MAIN        ; <code to reset your counter value>
            GOTO    LOOP_NXT
LOOP        CALL    ACTIVE
            ; <code to increment your counter value>
LOOP_NXT    ; <code to display your counter value>
            CALL    INACTIVE
            GOTO    LOOP

The above code resets your counter value to whatever you want to start at and then it jumps into the loop where it displays the value and waits for the button to become inactive. The effect here is that if you start up your code with the button pressed (it should not be, but what if it is?), then the code will still reset the counter and display it... but it will wait until you release it before continuing. So you have to let up on the switch.

Then, once that has happened, the basic loop just waits for a debounced active state of the switch. When it sees that, it increments the counter immediately (on the press, not on the release) but then waits for the button to be released before continuing, again.

That's about it. You still need to write appropriate code for the counter and display. But that gets the idea across for the rest.

\$\endgroup\$
  • 1
    \$\begingroup\$ Mr. I have no words to thank you! Really! I already had it working with delays to take care of debouncing somehow, I just adapted all things to your code and it is almost done, I am just working on the reset button that is not working now (RA7), but that is a matter of time and analyzing. Thanks a lot! You saved my day. THANKS!!!!! \$\endgroup\$ – ERS Jul 2 '17 at 22:51
  • 1
    \$\begingroup\$ @ERS Glad to hear it helps out. Study it a bit and make sure you follow the logic well. If not, feel free to ask more questions. Also, don't forget to select an answer if you feel you have what you need (and if you feel you've waited long enough for others to add their thoughts, as well.) \$\endgroup\$ – jonk Jul 2 '17 at 23:28
  • \$\begingroup\$ I will definitely study it, since after the project is done, I will have to right a report on every aspects of it. Ok if I have any doubt I will reach you here. Thanks again \$\endgroup\$ – ERS Jul 2 '17 at 23:38
  • \$\begingroup\$ just two questions, any reason to repeat code in the INATIVE and ACTIVE routines? What exactly does, for example GOTO $-3 ? That minus 3? Thanks in advance \$\endgroup\$ – ERS Jul 3 '17 at 21:26
  • \$\begingroup\$ @ERS ACTIVE and INACTIVE aren't the same code. Note BTFSS and BTFSC. And I could just use a label in the other routine with the $-3 present, I suppose. The $ means "here" and the -3 means backwards three instruction words. \$\endgroup\$ – jonk Jul 3 '17 at 23:59
2
\$\begingroup\$

You need to "debounce" your switch. This can be done in hardware by adding a small capacitor across the input or in software by checking that the switch maintains its state for long enough.

Now the other problem is that I need to add the value of 1 once and only once while pressing the button. Imagine the button pressed by 1 minute and it must be not add more than the initial increment of 1.

Now you need a software "one-shot". You need to remember if the button was pressed the last time you looked.

// Pseudo code
if(button) {               // Button was pressed
  if(!buttonMemory) {      // Button was off last time we looked.
    counter ++;            // Increment the counter
    buttonMemory = true;   // Remember the button was pressed.
  }
} else {
  buttonMemory = false;    // Cancel the memory.
}
\$\endgroup\$
  • \$\begingroup\$ Thanks for your help, debounce problem its solved for now I guess. Now the other problem is that I need to add the value of 1 once and only once while pressing the button. Imagine the button pressed by 1 minute and it must be not add more than the initial increment of 1. I don't know if I am writing this in a confusing manner, english is not my main language, sorry. \$\endgroup\$ – ERS Jul 2 '17 at 14:19
  • \$\begingroup\$ See the update. Your English is quite clear. \$\endgroup\$ – Transistor Jul 2 '17 at 16:44
  • \$\begingroup\$ Thanks again. It makes complete sense in C, I will try to do it in assembly \$\endgroup\$ – ERS Jul 2 '17 at 16:57
  • \$\begingroup\$ @ERS Lots of ways to do the debouncing. On a more sophisticated microcontroller you would have edge sensitive interrupts and independent timers you can use to trigger NN millisecond dead time once your button interrupt fires. Get interrupt, set flag. Timer interrupt fires, clear flag. And incidentally you want resistor and a capacitor for a proper low-pass debouncer. \$\endgroup\$ – Barleyman Jul 2 '17 at 16:59
  • \$\begingroup\$ @Barleyman: All good stuff but the guy is just getting started. Let him master and understand the basics first. \$\endgroup\$ – Transistor Jul 2 '17 at 17:26
1
\$\begingroup\$

There are several issues. It seems from your vague description that you want to measure the time a button is pressed. You have two problems, debouncing the button and measuring the time the debounced button is down.

I would do both in a 1 ms periodic interrupt. Get used to doing that. A 1 ms clock tick is useful for lots of things.

On this particular processor, timer 2 with its built-in period register is well suited for this. Set it up to cause a interrupt every 1 ms (1 kHz rate).

In the interrupt routine, you first debounce the button. I like to use 50 ms as the debounce period. Most buttons bounce for around 10 ms, but I've seen some that bounce nearly 50 ms. Also, that's about the maximum time that humans don't notice as a delay, so there is no harm at 50 ms to the user experience.

Keep a flag that is the official debounced state of the button. When the button's instantaneous state is the same, reset the debounce counter to 50. when the button's instantaneous and debounced states differ, decrement the counter. When the counter reaches 0, change the debounced state to the current button state and reset the counter to 50.

Now that you have the debounced state of the button, simply count up whenever the button is pressed and do nothing when it is released. In the special case of a down to up transition, save the counter value and set a flag for the foreground code indicating a new measured length is available. Clear the counter.

This counter will need to be multiple bytes long. A single byte can only count to 255, which is about ¼ second. With two bytes, the button can be held down for a bit over a minute. That might be good enough. Just to be safe, you should only increment the counter when it's not already at maximum. That way a long button press just clips at the maximum representable value.

\$\endgroup\$
  • \$\begingroup\$ Hello, first of all, thanks for your reply. I am testing debouncing a button with the output of a led. So far so good. The second problem, must be solved with timer? I mean, I was told that in this work I need to avoid timers. Can it be something like while button pressed, add value one and nothing more. Some kind of interruption? \$\endgroup\$ – ERS Jul 2 '17 at 14:12
  • \$\begingroup\$ Add value one, one time only \$\endgroup\$ – ERS Jul 2 '17 at 14:16
  • \$\begingroup\$ By the way, the bouncing will be too fast to see on an LED. \$\endgroup\$ – gbarry Jul 2 '17 at 16:53
  • 1
    \$\begingroup\$ You can do a whole embedded control system with a task scheduler based on a system heartbeat timer. I like to use two timers, one for high level stuff with around 1ms (500 microseconds, whatever) period and another with short period for real time metrics, performance analysis, etc. \$\endgroup\$ – Barleyman Jul 2 '17 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.