Getting started in embedded firmware pt 3
It is now time for my favorite introductory lab. This lab is my favorite because it.... nope I will not spoil the surprise!
Today we will do two labs and we will learn about digital inputs, and pushbuttons.
First the digital input. The PIC16F18446 has 20 pins spread across 3 ports. PORTA, PORTB and PORTC. Last time we did not learn about the digital I/O pins but we did learn how to drive a pin as an output. It turns out that making a pin an output is pretty simple... Just clear the appropriate TRIS bit to zero and that pin is an output. It turns out that all the pins are inputs by default when the chip starts up. Additionally, many of the pins have an alternate analog function and the pin is configured as an analog input by default in order to keep the port in the lowest power state that cannot conflict with outside circuitry. To get a digital input, we need to leave the digital input function enabled (TRIS is 1) and turn the analog function off (ANSEL is 0). If we leave the analog function enabled, the digital function will always read a zero. Here is the port diagram from the data sheet.
You can see in the diagram that if the ANSELx signal is set to a 1 the leading to the data bus AND gate will always output a 0. (Remember, both inputs to an AND gate must be 1 for the output to be 1 and the little circle on the ANSELx signal inverts 1's to 0's). From this diagram we learn that ANSELx must be 0 and TRISx must be 1 or the output buffer connected to the TRISx signal will drive the pin.
It is time for some code. We will make a simple program that lights the LED when the button on our Curiosity is pressed and when the button is released, the LED will turn off. A quick peak at the schematic shows the button is on PORTC bit 2.
The Program Please:
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
if(PORTCbits.RC2)
{
LATAbits.LATA2 = 1;
}
else
{
LATAbits.LATA2 = 0;
}
}
}
A quick surprise, the WPUC register is the weak pull up control register for PORTC. This is not shown in the generic diagram but the explanation is simple. The pushbutton will connect the RC2 pin to ground which will create a 0 on the input. The weak pull up is required to make a 1 when the button is NOT pressed. Setting the WPUC2 bit will enable the weak pull up for RC2.
Programming.... Testing... Viola! it works,
But this is pretty boring after all, we could replace the Curiosity with a wire and save some money. It is time to make that computer earn its place in the design.
We are going to make a small change to the program so it will toggle the LED each time the button is pressed.
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
if(PORTCbits.RC2)
{
if(LATAbits.LATA2 == 1)
LATAbits.LATA2 = 0;
else
LATAbits.LATA2 = 1;
}
}
}
Well that is strange.... it seems like it works when I press the button. AH HA, RC2 is a 1 when the button is NOT pressed.... stand by...
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
if(PORTCbits.RC2==0)
{
if(LATAbits.LATA2 == 1)
LATAbits.LATA2 = 0;
else
LATAbits.LATA2 = 1;
}
}
}
Well darn, it almost works again. It seems like it is dim when I hold the button and when I release it, the LED is randomly on or off. AH HA!, It is toggling the LED as long as I hold the pin. We need to add a variable and only trigger the LED change when the pin changes state.
__bit oldRC2 = 0;
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
if(PORTCbits.RC2==0 && oldRC2 == 1)
{
if(LATAbits.LATA2 == 1)
LATAbits.LATA2 = 0;
else
LATAbits.LATA2 = 1;
}
oldRC2 = PORTCbits.RC2;
}
}
I added a variable called oldRC2. I used the compiler built-in type __bit to represent a single bit of data (matching the data size of a single pin) and the LED is triggered when the button is pressed (RC2 == 0) AND the button was previously NOT pressed (oldRC2 == 1). The value of oldRC2 is set to be RC2 after the testing.
Well Heck... It is getting closer but something is still not quite right. I press the button and the LED changes state... usually... sometimes...
I see the problem. The pin RC2 is sampled twice. Once at the beginning where it is compared to the oldRC2 and once a bit later to make a new copy for oldRC2. What if the value on RC2 changed between these two samplings. That would mean that we could miss an edge. The solution is simple.
__bit oldRC2 = 0;
__bit newRC2 = 0;
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
newRC2 = PORTCbits.RC2;
if(newRC2==0 && oldRC2 == 1)
{
if(LATAbits.LATA2 == 1)
LATAbits.LATA2 = 0;
else
LATAbits.LATA2 = 1;
}
oldRC2 = newRC2;
}
}
We will just create a new variable and sample RC2 once into the variable newRC2. Then we will use that for the comparison and the assignment. Ah, this seems to be pretty good.
Time for some more extensive testing... This is strange. If I press this many times, every so often it looks like a button press was skipped. Let us put the logic analyzer on this problem.
Ok, the top trace is RC2(the button) and the bottom trace is RA2 (the LED). Every falling edge of RC2 the LED changes state (on to off, or off to on). But look right in the middle. It appears that the LED changed state on the rising edge as well as the falling edge. Perhaps we should look at that spot more closely.
Look at that. An extra transition on the button. It turns out that buttons are made of two pieces of metal that are pressed together. Sometimes the metal will bounce and break the connection. If we measure this bounce we find that it is nearly 20useconds wide ( 0.000020 seconds).
That is pretty fast but the PIC16F18446 detected the bounce and changed the LED state. If you research button bouncing you will find that this phenomenon is on ALL buttons, but some buttons are much worse than others. This button is actually pretty good and it took a large number of tries before I was caught by it.
This button is very good. So I will do the simplest button debouncer I have ever done.
__bit oldRC2 = 0;
__bit newRC2 = 0;
void main(void) {
TRISAbits.TRISA2 = 0;
ANSELCbits.ANSC2 = 0;
WPUCbits.WPUC2 = 1;
while(1)
{
newRC2 = PORTCbits.RC2 && PORTCbits.RC2;
if(newRC2==0 && oldRC2 == 1)
{
if(LATAbits.LATA2 == 1)
LATAbits.LATA2 = 0;
else
LATAbits.LATA2 = 1;
}
oldRC2 = newRC2;
}
}
I will simply read PORTC twice and let it read a one if it reads high on BOTH reads. Note the AND function between the two reads of RC2.
That is all for today. Your homework is to do some research on different ways to solve the debounce problem. Next week we will introduce our first peripheral.
- 1
0 Comments
Recommended Comments
There are no comments to display.