2 HARDWARE DEBOUNCING Microcontrollers and Interfacing week 8 exercises 1 More digital input When using a switch for digital input we always need a pull-up resistor. For convenience, the microcontroller can provide the pull-up resistance for us internally. For any digital pin configured as INPUT we can use digitalwrite(pin, HIGH) to enable the internal pull-up resistor, and digitalwrite(pin, LOW) to disable the internal pull-up resistor. Alternatively, the pin s mode can be set to INPUT PULLUP to configure it for input and turn on the pull-up resistor at the same time. For example, pinmode (11, INPUT_PULLUP ); and pinmode (11, INPUT ); digitalwrite (11, HIGH ); are equivalent. Modify your circuit to work with internal pull-up resistors. (We will be using pins 4 through 10 for the seven LED segments, and using pins 2 and 3 for the switch inputs so that the circuit is suitable for the interrupt experiment at the end of today s class.) An example of a suitable circuit is shown on the right. Modify your sketch to use internal pull-up resistors on pins 2 and 3 for switch inputs, and pins 4 through 10 as outputs driving the seven-segment LED display. (An example of a suitable sketch for this experiment is given on page 4. Please feel free to copy it to save time.) Operate the circuit for a while. If you cannot observe bouncing behaviour on the buttons, try using different switches (ask the instructor for some). Hardware debouncing Arduino Try to cure the switch bounce by attaching a small capacitor across the internal pull-ups D12 debouncing capacitor Make a note of the smallest value that actually cures the bounce. Smallest capacitor value that cures switch bounce: 1 of 8 INPUT_PULLUP F. The value of the internal pull-up resistor is approximately 20k Ω. Calculate the 50% time constant of the RC circuit formed by the internal pull-up resistor and the capacitor. RC circuit 50% time constant: τ50 = R C 0.7 = D11 s. external switches GND 20k 5V switch contacts. Begin with the largest value you have. Then try successively smaller values until the switch starts to bounce again. 20k 2
3 SOFTWARE DEBOUNCING 3 Software debouncing One reason microcontrollers are useful is that they let us move hardware into software. Let s try to debounce the switch in software instead of in hardware. 3.1 Debouncing with a fixed delay A simple approach to debouncing is to introduce a short delay, to allow the switch to finish bouncing, before letting the sketch proceed. Remove the debouncing capacitor(s) from your circuit. Modify your sketch to implement the following debouncing strategy: if the input pin is LOW then do nothing (the switch is not pressed); otherwise perform the associated action (increment or decrement the counter) wait for a short time (using the delay() function) to let the switch settle use a while loop to wait for the input pin to return to HIGH (If you cannot easily write this yourself, an example sketch can be found on page 5.) Observe the behaviour of the sketch. Is the debouncing reliable? Can you press the switch rapidly, and have the counter accurately record each press? Can you press both switches at the same time, and obtain the expected result? 3.2 Debouncing with a timer: simulating the hardware debounce in software A slightly better solution is to simulate the capacitor circuit in software, using a timer. To implement a timer we can use the millis() function that returns the number of milliseconds for which the sketch has been running. By remembering the millisecond time at which the timer is started, and then comparing the current value of millis() with the starting value, we can use a simple loop to wait until the the timer expires (when the difference between the current millis() and the start time exceeds the number of milliseconds we are trying to time). event capacitor circuit timer simulation switch pressed capacitor discharges instantly timer is reset to 0 perform action (modify counter) perform action (modify counter) switch bounces open: capacitor begins to charge open: timer runs closed: capacitor discharged closed: timer is reset switch closed capacitor remains discharged timer repeatedly reset to zero switch opened capacitor begins to charge timer begins to run after τ, input goes HIGH when timer completes, sketch proceeds Modify your sketch to debounce using a timer. An outline of a possible implementation of this strategy might be as follows: if (switch is closed, i.e., button is pressed ) perform button pressed action unsigned long start= millis(); // initialise timer while ( millis() - start < timer duration) // timer has not expired if (button is pressed ) // bouncing, or settled in closed state start= millis(); // reset timer to zero // we now know input has remained HIGH ( switch open) for the timer duration: // switch cycle is complete, so continue with rest of sketch... You can experiment with different timer values. A good value to start with would be the 50% time constant that you calculated in section 2. (If you cannot complete the sketch by yourself, an example solution is shown on page 6.) 2 of 8
4 INTERRUPTS 4 Interrupts If our loop() has a repetitive task to perform then trying to handle switches and other asynchronous input within loop() can be inconvenient. Just like all CPUs, the microcontroller provides interrupts for handling asynchronous events (such as an input pin changing state) outside the normal cycle of repetitive tasks. We will use interupt 0 (associated with input pin 2) and interupt 1 (associated with input pin 3) to process our switch inputs asynchronously. Make your counter variable global. Declare it to be volatile. Write an interrupt service routine (handler) called buttondownisr() that decrements the counter and calls display() to display the new value. Then modify your setup() function so that it uses attachinterrupt(0, buttondownisr, FALLING) to associate your handler with interrupt 0 (pin 2), triggering the handler whenever the input changes from HIGH to LOW. Write a similar handler called buttonupisr() that increments the counter, and then in setup() use attachinterrupt() to associate it with interrupt 1 (pin 3). Test your sketch with an empty loop() function; the buttons should modify the counter as expected when whe loop() is empty. Add a repetitive task to loop(), such as blinking the LED attached to pin 13, to demonstrate that the button handling does not interfere with synchronous tasks. (If you have trouble completing this part on your own, you can find an example solution on page 7.) 4.1 Challenge (easy) If you have time left over, try to implement the repetitive task (blinking the pin 13 LED) in the earlier sketch that used delay() or a timer to debounce the buttons. Which sketch is conceptually simpler and easier to understand? 4.2 Challenge (difficult!) Debounce the switches in the interrupt-based sketch. 3 of 8
5 SKETCHES 5 Sketches 5.1 Seven-segment up/down counter const int BUTTONDOWN = 2; const int BUTTONUP = 3; const int SEGMENTS = 4; void display( int digit) static unsigned char digits []= 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 ; int bits= ( digit < 0)? 0 : digits[ digit % 10]; for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i, bits >>= 1) digitalwrite(i, ( bits & 1)? HIGH : LOW); void setup() for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i) pinmode(i, OUTPUT); display (0); pinmode( BUTTONDOWN, INPUT_PULLUP); pinmode( BUTTONUP, INPUT_PULLUP); void loop() static unsigned int i= 0; while (1) if ( digitalread( BUTTONUP) == LOW) display (++i % 10); while ( digitalread( BUTTONUP) == LOW); if ( digitalread( BUTTONDOWN) == LOW) display(--i % 10); while ( digitalread( BUTTONDOWN) == LOW); 4 of 8
5.2 Seven-segment up/down counter with delay debounce 5 SKETCHES 5.2 Seven-segment up/down counter with delay debounce const int BUTTONDOWN = 2; const int BUTTONUP = 3; const int SEGMENTS = 4; void display( int digit) static unsigned char digits []= 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 ; int bits= ( digit < 0)? 0 : digits[ digit % 10]; for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i, bits >>= 1) digitalwrite(i, ( bits & 1)? HIGH : LOW); void setup() for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i) pinmode(i, OUTPUT); display (0); pinmode( BUTTONDOWN, INPUT_PULLUP); pinmode( BUTTONUP, INPUT_PULLUP); void loop() static unsigned int i= 0; while (1) if ( digitalread( BUTTONUP) == LOW) display (++i % 10); delay (100); while ( digitalread( BUTTONUP) == LOW); if ( digitalread( BUTTONDOWN) == LOW) display(--i % 10); delay (100); while ( digitalread( BUTTONDOWN) == LOW); 5 of 8
5.3 Seven-segment up/down counter with timer debounce 5 SKETCHES 5.3 Seven-segment up/down counter with timer debounce const int BUTTONDOWN = 2; const int BUTTONUP = 3; const int SEGMENTS = 4; void display( int digit) static unsigned char digits []= 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 ; int bits= ( digit < 0)? 0 : digits[ digit % 10]; for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i, bits >>= 1) digitalwrite(i, ( bits & 1)? HIGH : LOW); void setup() for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i) pinmode(i, OUTPUT); display (0); pinmode( BUTTONDOWN, INPUT_PULLUP); pinmode( BUTTONUP, INPUT_PULLUP); const int DEBOUNCE = 5; // milliseconds void loop() static int i= 0; while (1) if ( digitalread( BUTTONUP) == LOW) ++i; display(i); unsigned long start = millis(); while ( millis() - start < DEBOUNCE) if ( digitalread( BUTTONUP) == LOW) start= millis(); if ( digitalread( BUTTONDOWN) == LOW) --i; while (i < 0) i+= 10; display(i); unsigned long start = millis(); while ( millis() - start < DEBOUNCE) if ( digitalread( BUTTONDOWN) == LOW) start= millis(); 6 of 8
5.4 Seven-segment up/down counter using interrupts 5 SKETCHES 5.4 Seven-segment up/down counter using interrupts const int BUTTONDOWN = 2; const int BUTTONUP = 3; const int SEGMENTS = 4; void display( int digit) static unsigned char digits []= 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 ; int bits= ( digit < 0)? 0 : digits[ digit % 10]; for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i, bits >>= 1) digitalwrite(i, ( bits & 1)? HIGH : LOW); void setup() for ( int i= SEGMENTS; i < SEGMENTS + 7; ++i) pinmode(i, OUTPUT); display (0); pinmode( BUTTONDOWN, INPUT_PULLUP); pinmode( BUTTONUP, INPUT_PULLUP); attachinterrupt( BUTTONDOWN - 2, buttondownisr, FALLING); attachinterrupt( BUTTONUP - 2, buttonupisr, FALLING); pinmode(13, OUTPUT); // for demonstration of concurrent activity volatile static int counter = 0; void buttondownisr () if (-- counter < 0) counter += 10; display( counter); void buttonupisr() ++ counter; display( counter); void loop() for (;;) digitalwrite (13, HIGH); delay (500); digitalwrite (13, LOW ); delay (500); 7 of 8
A CAPACITORS A Capacitors A capacitor is a two-terminal device that stores electrical charge. When it is empty it has a very low resistance to current, and allows a large current to flow into it. As it fills up the resistance increases, and the current decreases. Eventually the capacitor is full and the current flowing into it decreases to zero, effectively giving it an infinitely large resistance. When placed in a circuit with a resistor, the capacitor behaves like a low-value resistor that gradually increases in resistance as charge flows into it. Considering the two components as a voltage divider, this means the voltage across the capacitor begins at zero (R R C ) and rises until it equals the supply voltage (R R C ). The current flowing into the capacitor, and hence the rate at which it accumulates charge and increases its voltage, is proportional to the voltage across its terminals. Therefore the rate of charging decreases as the amount of charge increases. This leads to an initial rapid increase in voltage that gradually slows as more and more charge is accumulated (and the voltage in the device increases, and the current flowing into it decreases). The time constant of a resistor-capacitor series circuit, τ (Greek letter tau ), is equal to the product of the resistor and capacitor values. The time constant indicates how long it will take for the capacitor to charge to 63% of its capacity (and hence to 63% of the supply voltage). Multiplying τ by various values tells us useful information about the time taken to reach various conditions: R C... τ 0.7 τ 1.0 τ 4.0 condition reached 50% of final voltage 63% of final voltage 98% of final voltage Reference 5V R I C V C C GND Examples of capacitor applications include timing (for oscillators, timers, switch debouncing circuits), blocking DC voltage while allowing AC signals to pass (in series connection), and stabilising voltage against short-term fluctuations (in parallel connection). A.1 Capacitor values and markings Capacitance is measured in Farads (after the physicist Michael Faraday). Typical capacitor values fall in the picofarad (1 pf = 10 12 F), nanofarad (1 nf = 10 9 F) and microfarad (1 µf = 10 6 F) ranges. Capacitors are either marked with their value directly, or (similarly to resistors) are marked with three digits indicating a two-digit value and an exponent giving the value in pf. Larger values (more than a few µf) are often polarised, and will have the positive and negative terminals marked (and possibly have a positive lead that is longer than the negative, like LEDs). Unlike LEDs, connecting a polarised capacitor the wrong way round is very damgerous and can lead to explosive failure of the device. 2200 µf polarised 100 nf (10 10 4 pf) marking value 101 100 pf 100 pf 222 2200 pf 2.2 nf 103 10000 pf 10 nf 104 100000 pf 100 nf + 8 of 8